Skip to content

Commit

Permalink
pherry: Check authority set & Verify justification
Browse files Browse the repository at this point in the history
  • Loading branch information
kvinwang committed Jan 5, 2024
1 parent 5dd4a9a commit 7975bdd
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 99 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
.PHONY: all node pruntime e2e test clippy dist
.PHONY: all node pruntime e2e test clippy dist pherry

all: node pruntime e2e

node:
cargo build --release
pherry:
cargo build --release -p pherry
pruntime:
make -C standalone/pruntime
e2e:
Expand Down
3 changes: 3 additions & 0 deletions standalone/pherry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ serde_json = "1.0"
clap = { version = "4.0.32", features = ["derive"] }

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

phala-types = { path = "../../crates/phala-types" }
phala-pallets = { path = "../../pallets/phala" }
Expand Down
107 changes: 107 additions & 0 deletions standalone/pherry/src/authority.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use anyhow::{anyhow, bail, Context, Result};
use codec::Decode;
use hash_db::{HashDB, EMPTY_PREFIX};
use log::info;
use phactory_api::blocks::{AuthoritySet, AuthoritySetChange};
use phaxt::RelaychainApi;
use sc_consensus_grandpa::GrandpaJustification;
use sp_consensus_grandpa::{AuthorityList, SetId};
use sp_trie::trie_types::TrieDBBuilder;
use sp_trie::{MemoryDB, Trie};

use crate::types::UnsigedBlock;
use crate::{get_header_at, types::Header};

type VersionedAuthorityList = (u8, AuthorityList);

pub struct StorageKeys;

impl StorageKeys {
pub fn authorities_v0() -> &'static [u8] {
b":grandpa_authorities"
}
pub fn authorities_v1() -> Vec<u8> {
phaxt::dynamic::storage_key("Grandpa", "Authorities")
}
pub fn current_set_id() -> Vec<u8> {
phaxt::dynamic::storage_key("Grandpa", "CurrentSetId")
}
}

pub async fn get_authority_with_proof_at(
api: &RelaychainApi,
header: &Header,
) -> Result<AuthoritySetChange> {
let authority_proof = crate::chain_client::read_proofs(
api,
Some(header.hash()),
vec![
StorageKeys::authorities_v0(),
&StorageKeys::current_set_id(),
&StorageKeys::authorities_v1(),
],
)
.await?;
let mut mdb = MemoryDB::<sp_core::Blake2Hasher>::default();
for value in authority_proof.iter() {
mdb.insert(EMPTY_PREFIX, value);
}
let trie = TrieDBBuilder::new(&mdb, &header.state_root).build();

let id_key = StorageKeys::current_set_id();
let alt_authorities_key = StorageKeys::authorities_v1();
let old_authorities_key: &[u8] = StorageKeys::authorities_v0();

// Read auth list
let mut auth_list = None;
for key in [&alt_authorities_key, old_authorities_key] {
if let Some(value) = trie.get(key).context("Check grandpa authorities failed")? {
let list: AuthorityList = if key == old_authorities_key {
VersionedAuthorityList::decode(&mut value.as_slice())
.expect("Failed to decode VersionedAuthorityList")
.1
} else {
AuthorityList::decode(&mut value.as_slice())
.expect("Failed to decode AuthorityList")
};
auth_list = Some(list);
break;
}
}
let list = auth_list.ok_or_else(|| anyhow!("No grandpa authorities found"))?;

// Read auth id
let Ok(Some(id_value)) = trie.get(&id_key) else {
bail!("Check grandpa set id failed");
};
let id: SetId = Decode::decode(&mut id_value.as_slice()).context("Failed to decode set id")?;
Ok(AuthoritySetChange {
authority_set: AuthoritySet { list, id },
authority_proof,
})
}

pub async fn verify(api: &RelaychainApi, header: &Header, mut justifications: &[u8]) -> Result<()> {
if header.number == 0 {
return Ok(());
}
let prev_header = get_header_at(api, Some(header.number - 1)).await?.0;
let auth_set = get_authority_with_proof_at(api, &prev_header).await?;
let justification: GrandpaJustification<UnsigedBlock> =
Decode::decode(&mut justifications).context("Failed to decode justification")?;
if (
justification.justification.commit.target_hash,
justification.justification.commit.target_number,
) != (header.hash(), header.number)
{
bail!("Invalid commit target in grandpa justification");
}
justification
.verify(auth_set.authority_set.id, &auth_set.authority_set.list)
.context("Failed to verify justification")?;
info!(
"Verified grandpa justification at block {}, auth_id={}",
header.number, auth_set.authority_set.id
);
Ok(())
}
19 changes: 6 additions & 13 deletions standalone/pherry/src/headers_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ use crate::types::ConvertTo;
use crate::{types::Header, GRANDPA_ENGINE_ID};
use anyhow::{anyhow, Result};
use codec::{Decode, Encode};
use phaxt::{
subxt::{self, rpc::types::NumberOrHex},
BlockNumber, ParachainApi, RelaychainApi,
};
use phaxt::{BlockNumber, ParachainApi, RelaychainApi};
use reqwest::Response;
use std::borrow::Cow;
use std::io::{self, Read, Write};
Expand Down Expand Up @@ -322,12 +319,15 @@ pub async fn grab_headers(
.ok_or_else(|| anyhow!("No justification for block changing set_id"))?;
justifications = Some(just_data.convert_to());
}
Some(crate::get_authority_with_proof_at(api, hash).await?)
Some(crate::get_authority_with_proof_at(api, &header).await?)
} else {
None
};

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

skip_justitication = skip_justitication.saturating_sub(1);
last_set = set_id;
Expand Down Expand Up @@ -422,14 +422,7 @@ pub async fn fetch_genesis_info(
.await?
.0
.block;
let hash = api
.rpc()
.block_hash(Some(subxt::rpc::types::BlockNumber::from(
NumberOrHex::Number(genesis_block_number as _),
)))
.await?
.expect("No genesis block?");
let set_proof = crate::get_authority_with_proof_at(api, hash).await?;
let set_proof = crate::get_authority_with_proof_at(api, &genesis_block.header).await?;
Ok(GenesisBlockInfo {
block_header: genesis_block.header,
authority_set: set_proof.authority_set,
Expand Down
98 changes: 22 additions & 76 deletions standalone/pherry/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use anyhow::{anyhow, Context, Result};
use anyhow::{anyhow, bail, Context, Result};
use log::{debug, error, info, warn};
use phala_node_rpc_ext::MakeInto;
use phala_trie_storage::ser::StorageChanges;
Expand All @@ -7,7 +7,6 @@ use sp_core::{crypto::AccountId32, H256};
use std::cmp;
use std::convert::TryFrom;
use std::str::FromStr;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use tokio::time::sleep;

Expand All @@ -20,11 +19,12 @@ use phaxt::{
subxt::{self, tx::TxPayload},
RpcClient,
};
use sp_consensus_grandpa::{AuthorityList, SetId};
use sp_consensus_grandpa::SetId;
use subxt::config::{substrate::Era, Header as _};

type VersionedAuthorityList = (u8, AuthorityList);
pub use authority::get_authority_with_proof_at;

mod authority;
mod endpoint;
mod error;
mod msg_sync;
Expand All @@ -41,14 +41,13 @@ use crate::types::{
RelaychainApi, SrSigner,
};
use phactory_api::blocks::{
self, AuthoritySet, AuthoritySetChange, BlockHeader, BlockHeaderWithChanges, HeaderToSync,
StorageProof,
self, AuthoritySetChange, BlockHeader, BlockHeaderWithChanges, HeaderToSync, StorageProof,
};
use phactory_api::prpc::{self, InitRuntimeResponse, PhactoryInfo};
use phactory_api::pruntime_client;

use clap::Parser;
use headers_cache::Client as CacheClient;
use headers_cache::{fetch_genesis_info, Client as CacheClient};
use msg_sync::{Error as MsgSyncError, Receiver, Sender};
use notify_client::NotifyClient;
use phala_types::{AttestationProvider, AttestationReport, Collateral};
Expand Down Expand Up @@ -434,57 +433,6 @@ pub async fn batch_sync_storage_changes(
Ok(())
}

pub async fn get_authority_with_proof_at(
api: &RelaychainApi,
hash: Hash,
) -> Result<AuthoritySetChange> {
// Storage
let id_key = phaxt::dynamic::storage_key("Grandpa", "CurrentSetId");
let alt_authorities_key = phaxt::dynamic::storage_key("Grandpa", "Authorities");
let old_authorities_key: &[u8] = b":grandpa_authorities";

// Try the old grandpa storage key first if true. Otherwise try the new key first.
static OLD_AUTH_KEY_PRIOR: AtomicBool = AtomicBool::new(true);
let old_prior = OLD_AUTH_KEY_PRIOR.load(Ordering::Relaxed);
let authorities_key_candidates = if old_prior {
[old_authorities_key, &alt_authorities_key]
} else {
[&alt_authorities_key, old_authorities_key]
};
let mut authorities_key: &[u8] = &[];
let mut value = None;
for key in authorities_key_candidates {
if let Some(data) = api.rpc().storage(key, Some(hash)).await? {
authorities_key = key;
value = Some(data.0);
break;
}
OLD_AUTH_KEY_PRIOR.store(!old_prior, Ordering::Relaxed);
}
let value = value.ok_or_else(|| anyhow!("No grandpa authorities found"))?;
let list: AuthorityList = if authorities_key == old_authorities_key {
VersionedAuthorityList::decode(&mut value.as_slice())
.expect("Failed to decode VersionedAuthorityList")
.1
} else {
AuthorityList::decode(&mut value.as_slice()).expect("Failed to decode AuthorityList")
};

// Set id
let id = api.current_set_id(Some(hash)).await?;
// Proof
let proof = chain_client::read_proofs(
api,
Some(hash),
vec![old_authorities_key, &alt_authorities_key, &id_key],
)
.await?;
Ok(AuthoritySetChange {
authority_set: AuthoritySet { list, id },
authority_proof: proof,
})
}

async fn try_load_handover_proof(pr: &PrClient, api: &ParachainApi) -> Result<()> {
let info = pr.get_info(()).await?;
if info.safe_mode_level < 2 {
Expand Down Expand Up @@ -716,7 +664,8 @@ async fn batch_sync_block(
let mut authrotiy_change: Option<AuthoritySetChange> = None;
if let Some(change_at) = set_id_change_at {
if change_at == last_header_number {
authrotiy_change = Some(get_authority_with_proof_at(api, last_header_hash).await?);
authrotiy_change =
Some(get_authority_with_proof_at(api, &last_header.header).await?);
}
}

Expand All @@ -738,6 +687,15 @@ async fn batch_sync_block(
header.justification = None;
}
}
if let Some(last_header) = header_batch.last() {
let Some(justification) = &last_header.justification else {
bail!(
"No justification found for header {}",
last_header.header.number
);
};
authority::verify(api, &last_header.header, justification).await?;
}
let r = req_sync_header(pr, header_batch, authrotiy_change).await?;
info!(" ..sync_header: {:?}", r);
next_headernum = r.synced_to + 1;
Expand Down Expand Up @@ -982,22 +940,7 @@ async fn init_runtime(
};
let genesis_info = match genesis_info {
Some(genesis_info) => genesis_info,
None => {
let genesis_block = get_block_at(api, Some(start_header)).await?.0.block;
let hash = api
.rpc()
.block_hash(Some(subxt::rpc::types::BlockNumber::from(
NumberOrHex::Number(start_header as _),
)))
.await?
.expect("No genesis block?");
let set_proof = get_authority_with_proof_at(api, hash).await?;
blocks::GenesisBlockInfo {
block_header: genesis_block.header.clone(),
authority_set: set_proof.authority_set,
proof: set_proof.authority_proof,
}
}
None => fetch_genesis_info(api, start_header).await?,
};
let genesis_state = chain_client::fetch_genesis_storage(para_api).await?;
let mut debug_set_key = None;
Expand Down Expand Up @@ -1032,7 +975,10 @@ pub async fn attestation_to_report(
pccs_url: &str,
pccs_timeout_secs: u64,
) -> Result<Vec<u8>> {
info!("Processing attestation report provider={}", attestation.provider);
info!(
"Processing attestation report, provider={}",
attestation.provider
);
let report = match attestation.payload {
Some(payload) => Attestation::SgxIas {
ra_report: payload.report.as_bytes().to_vec(),
Expand Down
1 change: 1 addition & 0 deletions standalone/pherry/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub type BlockNumber = u32;
pub type Hash = sp_core::H256;
pub type Header = sp_runtime::generic::Header<BlockNumber, sp_runtime::traits::BlakeTwo256>;
pub type Block = SignedBlock<Header, OpaqueExtrinsic>;
pub type UnsigedBlock = sp_runtime::generic::Block<Header, OpaqueExtrinsic>;
// API: notify

#[derive(Serialize, Deserialize, Debug)]
Expand Down
Loading

0 comments on commit 7975bdd

Please sign in to comment.