Skip to content

Commit 7975bdd

Browse files
committed
pherry: Check authority set & Verify justification
1 parent 5dd4a9a commit 7975bdd

File tree

8 files changed

+152
-99
lines changed

8 files changed

+152
-99
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
.PHONY: all node pruntime e2e test clippy dist
1+
.PHONY: all node pruntime e2e test clippy dist pherry
22

33
all: node pruntime e2e
44

55
node:
66
cargo build --release
7+
pherry:
8+
cargo build --release -p pherry
79
pruntime:
810
make -C standalone/pruntime
911
e2e:

standalone/pherry/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ serde_json = "1.0"
1919
clap = { version = "4.0.32", features = ["derive"] }
2020

2121
sp-core = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "release-polkadot-v1.5.0" }
22+
sp-trie = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "release-polkadot-v1.5.0" }
2223
sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "release-polkadot-v1.5.0", package = "sp-runtime" }
2324
sp-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "release-polkadot-v1.5.0", default-features = false }
25+
sc-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "release-polkadot-v1.5.0", default-features = false }
2426
codec = { package = 'parity-scale-codec', version = "3.6.5" }
27+
hash-db = "0.16.0"
2528

2629
phala-types = { path = "../../crates/phala-types" }
2730
phala-pallets = { path = "../../pallets/phala" }

standalone/pherry/src/authority.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
use anyhow::{anyhow, bail, Context, Result};
2+
use codec::Decode;
3+
use hash_db::{HashDB, EMPTY_PREFIX};
4+
use log::info;
5+
use phactory_api::blocks::{AuthoritySet, AuthoritySetChange};
6+
use phaxt::RelaychainApi;
7+
use sc_consensus_grandpa::GrandpaJustification;
8+
use sp_consensus_grandpa::{AuthorityList, SetId};
9+
use sp_trie::trie_types::TrieDBBuilder;
10+
use sp_trie::{MemoryDB, Trie};
11+
12+
use crate::types::UnsigedBlock;
13+
use crate::{get_header_at, types::Header};
14+
15+
type VersionedAuthorityList = (u8, AuthorityList);
16+
17+
pub struct StorageKeys;
18+
19+
impl StorageKeys {
20+
pub fn authorities_v0() -> &'static [u8] {
21+
b":grandpa_authorities"
22+
}
23+
pub fn authorities_v1() -> Vec<u8> {
24+
phaxt::dynamic::storage_key("Grandpa", "Authorities")
25+
}
26+
pub fn current_set_id() -> Vec<u8> {
27+
phaxt::dynamic::storage_key("Grandpa", "CurrentSetId")
28+
}
29+
}
30+
31+
pub async fn get_authority_with_proof_at(
32+
api: &RelaychainApi,
33+
header: &Header,
34+
) -> Result<AuthoritySetChange> {
35+
let authority_proof = crate::chain_client::read_proofs(
36+
api,
37+
Some(header.hash()),
38+
vec![
39+
StorageKeys::authorities_v0(),
40+
&StorageKeys::current_set_id(),
41+
&StorageKeys::authorities_v1(),
42+
],
43+
)
44+
.await?;
45+
let mut mdb = MemoryDB::<sp_core::Blake2Hasher>::default();
46+
for value in authority_proof.iter() {
47+
mdb.insert(EMPTY_PREFIX, value);
48+
}
49+
let trie = TrieDBBuilder::new(&mdb, &header.state_root).build();
50+
51+
let id_key = StorageKeys::current_set_id();
52+
let alt_authorities_key = StorageKeys::authorities_v1();
53+
let old_authorities_key: &[u8] = StorageKeys::authorities_v0();
54+
55+
// Read auth list
56+
let mut auth_list = None;
57+
for key in [&alt_authorities_key, old_authorities_key] {
58+
if let Some(value) = trie.get(key).context("Check grandpa authorities failed")? {
59+
let list: AuthorityList = if key == old_authorities_key {
60+
VersionedAuthorityList::decode(&mut value.as_slice())
61+
.expect("Failed to decode VersionedAuthorityList")
62+
.1
63+
} else {
64+
AuthorityList::decode(&mut value.as_slice())
65+
.expect("Failed to decode AuthorityList")
66+
};
67+
auth_list = Some(list);
68+
break;
69+
}
70+
}
71+
let list = auth_list.ok_or_else(|| anyhow!("No grandpa authorities found"))?;
72+
73+
// Read auth id
74+
let Ok(Some(id_value)) = trie.get(&id_key) else {
75+
bail!("Check grandpa set id failed");
76+
};
77+
let id: SetId = Decode::decode(&mut id_value.as_slice()).context("Failed to decode set id")?;
78+
Ok(AuthoritySetChange {
79+
authority_set: AuthoritySet { list, id },
80+
authority_proof,
81+
})
82+
}
83+
84+
pub async fn verify(api: &RelaychainApi, header: &Header, mut justifications: &[u8]) -> Result<()> {
85+
if header.number == 0 {
86+
return Ok(());
87+
}
88+
let prev_header = get_header_at(api, Some(header.number - 1)).await?.0;
89+
let auth_set = get_authority_with_proof_at(api, &prev_header).await?;
90+
let justification: GrandpaJustification<UnsigedBlock> =
91+
Decode::decode(&mut justifications).context("Failed to decode justification")?;
92+
if (
93+
justification.justification.commit.target_hash,
94+
justification.justification.commit.target_number,
95+
) != (header.hash(), header.number)
96+
{
97+
bail!("Invalid commit target in grandpa justification");
98+
}
99+
justification
100+
.verify(auth_set.authority_set.id, &auth_set.authority_set.list)
101+
.context("Failed to verify justification")?;
102+
info!(
103+
"Verified grandpa justification at block {}, auth_id={}",
104+
header.number, auth_set.authority_set.id
105+
);
106+
Ok(())
107+
}

standalone/pherry/src/headers_cache.rs

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ use crate::types::ConvertTo;
22
use crate::{types::Header, GRANDPA_ENGINE_ID};
33
use anyhow::{anyhow, Result};
44
use codec::{Decode, Encode};
5-
use phaxt::{
6-
subxt::{self, rpc::types::NumberOrHex},
7-
BlockNumber, ParachainApi, RelaychainApi,
8-
};
5+
use phaxt::{BlockNumber, ParachainApi, RelaychainApi};
96
use reqwest::Response;
107
use std::borrow::Cow;
118
use std::io::{self, Read, Write};
@@ -322,12 +319,15 @@ pub async fn grab_headers(
322319
.ok_or_else(|| anyhow!("No justification for block changing set_id"))?;
323320
justifications = Some(just_data.convert_to());
324321
}
325-
Some(crate::get_authority_with_proof_at(api, hash).await?)
322+
Some(crate::get_authority_with_proof_at(api, &header).await?)
326323
} else {
327324
None
328325
};
329326

330327
let justification = justifications.and_then(|v| v.into_justification(GRANDPA_ENGINE_ID));
328+
if let Some(just) = &justification {
329+
crate::authority::verify(api, &header, just).await?;
330+
}
331331

332332
skip_justitication = skip_justitication.saturating_sub(1);
333333
last_set = set_id;
@@ -422,14 +422,7 @@ pub async fn fetch_genesis_info(
422422
.await?
423423
.0
424424
.block;
425-
let hash = api
426-
.rpc()
427-
.block_hash(Some(subxt::rpc::types::BlockNumber::from(
428-
NumberOrHex::Number(genesis_block_number as _),
429-
)))
430-
.await?
431-
.expect("No genesis block?");
432-
let set_proof = crate::get_authority_with_proof_at(api, hash).await?;
425+
let set_proof = crate::get_authority_with_proof_at(api, &genesis_block.header).await?;
433426
Ok(GenesisBlockInfo {
434427
block_header: genesis_block.header,
435428
authority_set: set_proof.authority_set,

standalone/pherry/src/lib.rs

Lines changed: 22 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use anyhow::{anyhow, Context, Result};
1+
use anyhow::{anyhow, bail, Context, Result};
22
use log::{debug, error, info, warn};
33
use phala_node_rpc_ext::MakeInto;
44
use phala_trie_storage::ser::StorageChanges;
@@ -7,7 +7,6 @@ use sp_core::{crypto::AccountId32, H256};
77
use std::cmp;
88
use std::convert::TryFrom;
99
use std::str::FromStr;
10-
use std::sync::atomic::{AtomicBool, Ordering};
1110
use std::time::Duration;
1211
use tokio::time::sleep;
1312

@@ -20,11 +19,12 @@ use phaxt::{
2019
subxt::{self, tx::TxPayload},
2120
RpcClient,
2221
};
23-
use sp_consensus_grandpa::{AuthorityList, SetId};
22+
use sp_consensus_grandpa::SetId;
2423
use subxt::config::{substrate::Era, Header as _};
2524

26-
type VersionedAuthorityList = (u8, AuthorityList);
25+
pub use authority::get_authority_with_proof_at;
2726

27+
mod authority;
2828
mod endpoint;
2929
mod error;
3030
mod msg_sync;
@@ -41,14 +41,13 @@ use crate::types::{
4141
RelaychainApi, SrSigner,
4242
};
4343
use phactory_api::blocks::{
44-
self, AuthoritySet, AuthoritySetChange, BlockHeader, BlockHeaderWithChanges, HeaderToSync,
45-
StorageProof,
44+
self, AuthoritySetChange, BlockHeader, BlockHeaderWithChanges, HeaderToSync, StorageProof,
4645
};
4746
use phactory_api::prpc::{self, InitRuntimeResponse, PhactoryInfo};
4847
use phactory_api::pruntime_client;
4948

5049
use clap::Parser;
51-
use headers_cache::Client as CacheClient;
50+
use headers_cache::{fetch_genesis_info, Client as CacheClient};
5251
use msg_sync::{Error as MsgSyncError, Receiver, Sender};
5352
use notify_client::NotifyClient;
5453
use phala_types::{AttestationProvider, AttestationReport, Collateral};
@@ -434,57 +433,6 @@ pub async fn batch_sync_storage_changes(
434433
Ok(())
435434
}
436435

437-
pub async fn get_authority_with_proof_at(
438-
api: &RelaychainApi,
439-
hash: Hash,
440-
) -> Result<AuthoritySetChange> {
441-
// Storage
442-
let id_key = phaxt::dynamic::storage_key("Grandpa", "CurrentSetId");
443-
let alt_authorities_key = phaxt::dynamic::storage_key("Grandpa", "Authorities");
444-
let old_authorities_key: &[u8] = b":grandpa_authorities";
445-
446-
// Try the old grandpa storage key first if true. Otherwise try the new key first.
447-
static OLD_AUTH_KEY_PRIOR: AtomicBool = AtomicBool::new(true);
448-
let old_prior = OLD_AUTH_KEY_PRIOR.load(Ordering::Relaxed);
449-
let authorities_key_candidates = if old_prior {
450-
[old_authorities_key, &alt_authorities_key]
451-
} else {
452-
[&alt_authorities_key, old_authorities_key]
453-
};
454-
let mut authorities_key: &[u8] = &[];
455-
let mut value = None;
456-
for key in authorities_key_candidates {
457-
if let Some(data) = api.rpc().storage(key, Some(hash)).await? {
458-
authorities_key = key;
459-
value = Some(data.0);
460-
break;
461-
}
462-
OLD_AUTH_KEY_PRIOR.store(!old_prior, Ordering::Relaxed);
463-
}
464-
let value = value.ok_or_else(|| anyhow!("No grandpa authorities found"))?;
465-
let list: AuthorityList = if authorities_key == old_authorities_key {
466-
VersionedAuthorityList::decode(&mut value.as_slice())
467-
.expect("Failed to decode VersionedAuthorityList")
468-
.1
469-
} else {
470-
AuthorityList::decode(&mut value.as_slice()).expect("Failed to decode AuthorityList")
471-
};
472-
473-
// Set id
474-
let id = api.current_set_id(Some(hash)).await?;
475-
// Proof
476-
let proof = chain_client::read_proofs(
477-
api,
478-
Some(hash),
479-
vec![old_authorities_key, &alt_authorities_key, &id_key],
480-
)
481-
.await?;
482-
Ok(AuthoritySetChange {
483-
authority_set: AuthoritySet { list, id },
484-
authority_proof: proof,
485-
})
486-
}
487-
488436
async fn try_load_handover_proof(pr: &PrClient, api: &ParachainApi) -> Result<()> {
489437
let info = pr.get_info(()).await?;
490438
if info.safe_mode_level < 2 {
@@ -716,7 +664,8 @@ async fn batch_sync_block(
716664
let mut authrotiy_change: Option<AuthoritySetChange> = None;
717665
if let Some(change_at) = set_id_change_at {
718666
if change_at == last_header_number {
719-
authrotiy_change = Some(get_authority_with_proof_at(api, last_header_hash).await?);
667+
authrotiy_change =
668+
Some(get_authority_with_proof_at(api, &last_header.header).await?);
720669
}
721670
}
722671

@@ -738,6 +687,15 @@ async fn batch_sync_block(
738687
header.justification = None;
739688
}
740689
}
690+
if let Some(last_header) = header_batch.last() {
691+
let Some(justification) = &last_header.justification else {
692+
bail!(
693+
"No justification found for header {}",
694+
last_header.header.number
695+
);
696+
};
697+
authority::verify(api, &last_header.header, justification).await?;
698+
}
741699
let r = req_sync_header(pr, header_batch, authrotiy_change).await?;
742700
info!(" ..sync_header: {:?}", r);
743701
next_headernum = r.synced_to + 1;
@@ -982,22 +940,7 @@ async fn init_runtime(
982940
};
983941
let genesis_info = match genesis_info {
984942
Some(genesis_info) => genesis_info,
985-
None => {
986-
let genesis_block = get_block_at(api, Some(start_header)).await?.0.block;
987-
let hash = api
988-
.rpc()
989-
.block_hash(Some(subxt::rpc::types::BlockNumber::from(
990-
NumberOrHex::Number(start_header as _),
991-
)))
992-
.await?
993-
.expect("No genesis block?");
994-
let set_proof = get_authority_with_proof_at(api, hash).await?;
995-
blocks::GenesisBlockInfo {
996-
block_header: genesis_block.header.clone(),
997-
authority_set: set_proof.authority_set,
998-
proof: set_proof.authority_proof,
999-
}
1000-
}
943+
None => fetch_genesis_info(api, start_header).await?,
1001944
};
1002945
let genesis_state = chain_client::fetch_genesis_storage(para_api).await?;
1003946
let mut debug_set_key = None;
@@ -1032,7 +975,10 @@ pub async fn attestation_to_report(
1032975
pccs_url: &str,
1033976
pccs_timeout_secs: u64,
1034977
) -> Result<Vec<u8>> {
1035-
info!("Processing attestation report provider={}", attestation.provider);
978+
info!(
979+
"Processing attestation report, provider={}",
980+
attestation.provider
981+
);
1036982
let report = match attestation.payload {
1037983
Some(payload) => Attestation::SgxIas {
1038984
ra_report: payload.report.as_bytes().to_vec(),

standalone/pherry/src/types.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub type BlockNumber = u32;
2424
pub type Hash = sp_core::H256;
2525
pub type Header = sp_runtime::generic::Header<BlockNumber, sp_runtime::traits::BlakeTwo256>;
2626
pub type Block = SignedBlock<Header, OpaqueExtrinsic>;
27+
pub type UnsigedBlock = sp_runtime::generic::Block<Header, OpaqueExtrinsic>;
2728
// API: notify
2829

2930
#[derive(Serialize, Deserialize, Debug)]

0 commit comments

Comments
 (0)