Check that the validation code matches the parachain code (#3433)

This introduces a check to ensure that the parachain code matches the
validation code stored in the relay chain state. If not, it will print a
warning. This should be mainly useful for parachain builders to make
sure they have setup everything correctly.
This commit is contained in:
Bastian Köcher
2024-02-22 14:35:00 +01:00
committed by GitHub
parent 31546c8d24
commit 9bf1a5e238
10 changed files with 173 additions and 13 deletions
@@ -33,12 +33,12 @@ use cumulus_relay_chain_interface::RelayChainInterface;
use polkadot_node_primitives::CollationResult; use polkadot_node_primitives::CollationResult;
use polkadot_overseer::Handle as OverseerHandle; use polkadot_overseer::Handle as OverseerHandle;
use polkadot_primitives::{CollatorPair, Id as ParaId}; use polkadot_primitives::{CollatorPair, Id as ParaId, ValidationCode};
use futures::{channel::mpsc::Receiver, prelude::*}; use futures::{channel::mpsc::Receiver, prelude::*};
use sc_client_api::{backend::AuxStore, BlockBackend, BlockOf}; use sc_client_api::{backend::AuxStore, BlockBackend, BlockOf};
use sc_consensus::BlockImport; use sc_consensus::BlockImport;
use sp_api::ProvideRuntimeApi; use sp_api::{CallApiAt, ProvideRuntimeApi};
use sp_application_crypto::AppPublic; use sp_application_crypto::AppPublic;
use sp_blockchain::HeaderBackend; use sp_blockchain::HeaderBackend;
use sp_consensus::SyncOracle; use sp_consensus::SyncOracle;
@@ -47,6 +47,7 @@ use sp_core::crypto::Pair;
use sp_inherents::CreateInherentDataProviders; use sp_inherents::CreateInherentDataProviders;
use sp_keystore::KeystorePtr; use sp_keystore::KeystorePtr;
use sp_runtime::traits::{Block as BlockT, Header as HeaderT, Member}; use sp_runtime::traits::{Block as BlockT, Header as HeaderT, Member};
use sp_state_machine::Backend as _;
use std::{convert::TryFrom, sync::Arc, time::Duration}; use std::{convert::TryFrom, sync::Arc, time::Duration};
use crate::collator as collator_util; use crate::collator as collator_util;
@@ -100,6 +101,7 @@ where
+ AuxStore + AuxStore
+ HeaderBackend<Block> + HeaderBackend<Block>
+ BlockBackend<Block> + BlockBackend<Block>
+ CallApiAt<Block>
+ Send + Send
+ Sync + Sync
+ 'static, + 'static,
@@ -172,6 +174,22 @@ where
continue continue
} }
let Ok(Some(code)) =
params.para_client.state_at(parent_hash).map_err(drop).and_then(|s| {
s.storage(&sp_core::storage::well_known_keys::CODE).map_err(drop)
})
else {
continue;
};
super::check_validation_code_or_log(
&ValidationCode::from(code).hash(),
params.para_id,
&params.relay_client,
*request.relay_parent(),
)
.await;
let relay_parent_header = let relay_parent_header =
match params.relay_client.header(RBlockId::hash(*request.relay_parent())).await { match params.relay_client.header(RBlockId::hash(*request.relay_parent())).await {
Err(e) => reject_with_error!(e), Err(e) => reject_with_error!(e),
@@ -290,10 +290,7 @@ where
// If the longest chain has space, build upon that. Otherwise, don't // If the longest chain has space, build upon that. Otherwise, don't
// build at all. // build at all.
potential_parents.sort_by_key(|a| a.depth); potential_parents.sort_by_key(|a| a.depth);
let initial_parent = match potential_parents.pop() { let Some(initial_parent) = potential_parents.pop() else { continue };
None => continue,
Some(p) => p,
};
// Build in a loop until not allowed. Note that the authorities can change // Build in a loop until not allowed. Note that the authorities can change
// at any block, so we need to re-claim our slot every time. // at any block, so we need to re-claim our slot every time.
@@ -301,6 +298,10 @@ where
let mut parent_header = initial_parent.header; let mut parent_header = initial_parent.header;
let overseer_handle = &mut params.overseer_handle; let overseer_handle = &mut params.overseer_handle;
// We mainly call this to inform users at genesis if there is a mismatch with the
// on-chain data.
collator.collator_service().check_block_status(parent_hash, &parent_header);
// This needs to change to support elastic scaling, but for continuously // This needs to change to support elastic scaling, but for continuously
// scheduled chains this ensures that the backlog will grow steadily. // scheduled chains this ensures that the backlog will grow steadily.
for n_built in 0..2 { for n_built in 0..2 {
@@ -353,6 +354,14 @@ where
Some(v) => v, Some(v) => v,
}; };
super::check_validation_code_or_log(
&validation_code_hash,
params.para_id,
&params.relay_client,
relay_parent,
)
.await;
match collator match collator
.collate( .collate(
&parent_header, &parent_header,
@@ -20,5 +20,60 @@
//! included parachain block, as well as the [`lookahead`] collator, which prospectively //! included parachain block, as well as the [`lookahead`] collator, which prospectively
//! builds on parachain blocks which have not yet been included in the relay chain. //! builds on parachain blocks which have not yet been included in the relay chain.
use cumulus_relay_chain_interface::RelayChainInterface;
use polkadot_primitives::{
Hash as RHash, Id as ParaId, OccupiedCoreAssumption, ValidationCodeHash,
};
pub mod basic; pub mod basic;
pub mod lookahead; pub mod lookahead;
/// Check the `local_validation_code_hash` against the validation code hash in the relay chain
/// state.
///
/// If the code hashes do not match, it prints a warning.
async fn check_validation_code_or_log(
local_validation_code_hash: &ValidationCodeHash,
para_id: ParaId,
relay_client: &impl RelayChainInterface,
relay_parent: RHash,
) {
let state_validation_code_hash = match relay_client
.validation_code_hash(relay_parent, para_id, OccupiedCoreAssumption::Included)
.await
{
Ok(hash) => hash,
Err(error) => {
tracing::debug!(
target: super::LOG_TARGET,
%error,
?relay_parent,
%para_id,
"Failed to fetch validation code hash",
);
return
},
};
match state_validation_code_hash {
Some(state) =>
if state != *local_validation_code_hash {
tracing::warn!(
target: super::LOG_TARGET,
%para_id,
?relay_parent,
?local_validation_code_hash,
relay_validation_code_hash = ?state,
"Parachain code doesn't match validation code stored in the relay chain state",
);
},
None => {
tracing::warn!(
target: super::LOG_TARGET,
%para_id,
?relay_parent,
"Could not find validation code for parachain in the relay chain state.",
);
},
}
}
@@ -136,6 +136,15 @@ impl RelayChainInterface for Relaychain {
Ok(Some(PersistedValidationData { parent_head, ..Default::default() })) Ok(Some(PersistedValidationData { parent_head, ..Default::default() }))
} }
async fn validation_code_hash(
&self,
_: PHash,
_: ParaId,
_: OccupiedCoreAssumption,
) -> RelayChainResult<Option<ValidationCodeHash>> {
unimplemented!("Not needed for test")
}
async fn candidate_pending_availability( async fn candidate_pending_availability(
&self, &self,
_: PHash, _: PHash,
+9
View File
@@ -117,6 +117,15 @@ impl RelayChainInterface for DummyRelayChainInterface {
})) }))
} }
async fn validation_code_hash(
&self,
_: PHash,
_: ParaId,
_: OccupiedCoreAssumption,
) -> RelayChainResult<Option<ValidationCodeHash>> {
unimplemented!("Not needed for test")
}
async fn candidate_pending_availability( async fn candidate_pending_availability(
&self, &self,
_: PHash, _: PHash,
@@ -21,7 +21,7 @@ use cumulus_primitives_core::{
relay_chain::{ relay_chain::{
runtime_api::ParachainHost, Block as PBlock, BlockId, CommittedCandidateReceipt, runtime_api::ParachainHost, Block as PBlock, BlockId, CommittedCandidateReceipt,
Hash as PHash, Header as PHeader, InboundHrmpMessage, OccupiedCoreAssumption, SessionIndex, Hash as PHash, Header as PHeader, InboundHrmpMessage, OccupiedCoreAssumption, SessionIndex,
ValidatorId, ValidationCodeHash, ValidatorId,
}, },
InboundDownwardMessage, ParaId, PersistedValidationData, InboundDownwardMessage, ParaId, PersistedValidationData,
}; };
@@ -115,6 +115,19 @@ impl RelayChainInterface for RelayChainInProcessInterface {
)?) )?)
} }
async fn validation_code_hash(
&self,
hash: PHash,
para_id: ParaId,
occupied_core_assumption: OccupiedCoreAssumption,
) -> RelayChainResult<Option<ValidationCodeHash>> {
Ok(self.full_client.runtime_api().validation_code_hash(
hash,
para_id,
occupied_core_assumption,
)?)
}
async fn candidate_pending_availability( async fn candidate_pending_availability(
&self, &self,
hash: PHash, hash: PHash,
@@ -30,7 +30,7 @@ use cumulus_primitives_core::relay_chain::BlockId;
pub use cumulus_primitives_core::{ pub use cumulus_primitives_core::{
relay_chain::{ relay_chain::{
CommittedCandidateReceipt, Hash as PHash, Header as PHeader, InboundHrmpMessage, CommittedCandidateReceipt, Hash as PHash, Header as PHeader, InboundHrmpMessage,
OccupiedCoreAssumption, SessionIndex, ValidatorId, OccupiedCoreAssumption, SessionIndex, ValidationCodeHash, ValidatorId,
}, },
InboundDownwardMessage, ParaId, PersistedValidationData, InboundDownwardMessage, ParaId, PersistedValidationData,
}; };
@@ -194,6 +194,15 @@ pub trait RelayChainInterface: Send + Sync {
relay_parent: PHash, relay_parent: PHash,
relevant_keys: &Vec<Vec<u8>>, relevant_keys: &Vec<Vec<u8>>,
) -> RelayChainResult<StorageProof>; ) -> RelayChainResult<StorageProof>;
/// Returns the validation code hash for the given `para_id` using the given
/// `occupied_core_assumption`.
async fn validation_code_hash(
&self,
relay_parent: PHash,
para_id: ParaId,
occupied_core_assumption: OccupiedCoreAssumption,
) -> RelayChainResult<Option<ValidationCodeHash>>;
} }
#[async_trait] #[async_trait]
@@ -301,4 +310,15 @@ where
async fn header(&self, block_id: BlockId) -> RelayChainResult<Option<PHeader>> { async fn header(&self, block_id: BlockId) -> RelayChainResult<Option<PHeader>> {
(**self).header(block_id).await (**self).header(block_id).await
} }
async fn validation_code_hash(
&self,
relay_parent: PHash,
para_id: ParaId,
occupied_core_assumption: OccupiedCoreAssumption,
) -> RelayChainResult<Option<ValidationCodeHash>> {
(**self)
.validation_code_hash(relay_parent, para_id, occupied_core_assumption)
.await
}
} }
@@ -19,7 +19,7 @@ use core::time::Duration;
use cumulus_primitives_core::{ use cumulus_primitives_core::{
relay_chain::{ relay_chain::{
CommittedCandidateReceipt, Hash as RelayHash, Header as RelayHeader, InboundHrmpMessage, CommittedCandidateReceipt, Hash as RelayHash, Header as RelayHeader, InboundHrmpMessage,
OccupiedCoreAssumption, SessionIndex, ValidatorId, OccupiedCoreAssumption, SessionIndex, ValidationCodeHash, ValidatorId,
}, },
InboundDownwardMessage, ParaId, PersistedValidationData, InboundDownwardMessage, ParaId, PersistedValidationData,
}; };
@@ -110,6 +110,17 @@ impl RelayChainInterface for RelayChainRpcInterface {
.await .await
} }
async fn validation_code_hash(
&self,
hash: RelayHash,
para_id: ParaId,
occupied_core_assumption: OccupiedCoreAssumption,
) -> RelayChainResult<Option<ValidationCodeHash>> {
self.rpc_client
.validation_code_hash(hash, para_id, occupied_core_assumption)
.await
}
async fn candidate_pending_availability( async fn candidate_pending_availability(
&self, &self,
hash: RelayHash, hash: RelayHash,
@@ -647,6 +647,20 @@ impl RelayChainRpcClient {
.await .await
} }
pub async fn validation_code_hash(
&self,
at: RelayHash,
para_id: ParaId,
occupied_core_assumption: OccupiedCoreAssumption,
) -> Result<Option<ValidationCodeHash>, RelayChainError> {
self.call_remote_runtime_function(
"ParachainHost_validation_code_hash",
at,
Some((para_id, occupied_core_assumption)),
)
.await
}
fn send_register_message_to_worker( fn send_register_message_to_worker(
&self, &self,
message: RpcDispatcherMessage, message: RpcDispatcherMessage,
+6 -4
View File
@@ -1887,18 +1887,20 @@ async fn background_validate_and_make_available<Context>(
if rp_state.awaiting_validation.insert(candidate_hash) { if rp_state.awaiting_validation.insert(candidate_hash) {
// spawn background task. // spawn background task.
let bg = async move { let bg = async move {
if let Err(e) = validate_and_make_available(params).await { if let Err(error) = validate_and_make_available(params).await {
if let Error::BackgroundValidationMpsc(error) = e { if let Error::BackgroundValidationMpsc(error) = error {
gum::debug!( gum::debug!(
target: LOG_TARGET, target: LOG_TARGET,
?candidate_hash,
?error, ?error,
"Mpsc background validation mpsc died during validation- leaf no longer active?" "Mpsc background validation mpsc died during validation- leaf no longer active?"
); );
} else { } else {
gum::error!( gum::error!(
target: LOG_TARGET, target: LOG_TARGET,
"Failed to validate and make available: {:?}", ?candidate_hash,
e ?error,
"Failed to validate and make available",
); );
} }
} }