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_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 sc_client_api::{backend::AuxStore, BlockBackend, BlockOf};
use sc_consensus::BlockImport;
use sp_api::ProvideRuntimeApi;
use sp_api::{CallApiAt, ProvideRuntimeApi};
use sp_application_crypto::AppPublic;
use sp_blockchain::HeaderBackend;
use sp_consensus::SyncOracle;
@@ -47,6 +47,7 @@ use sp_core::crypto::Pair;
use sp_inherents::CreateInherentDataProviders;
use sp_keystore::KeystorePtr;
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 crate::collator as collator_util;
@@ -100,6 +101,7 @@ where
+ AuxStore
+ HeaderBackend<Block>
+ BlockBackend<Block>
+ CallApiAt<Block>
+ Send
+ Sync
+ 'static,
@@ -172,6 +174,22 @@ where
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 =
match params.relay_client.header(RBlockId::hash(*request.relay_parent())).await {
Err(e) => reject_with_error!(e),
@@ -290,10 +290,7 @@ where
// If the longest chain has space, build upon that. Otherwise, don't
// build at all.
potential_parents.sort_by_key(|a| a.depth);
let initial_parent = match potential_parents.pop() {
None => continue,
Some(p) => p,
};
let Some(initial_parent) = potential_parents.pop() else { continue };
// 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.
@@ -301,6 +298,10 @@ where
let mut parent_header = initial_parent.header;
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
// scheduled chains this ensures that the backlog will grow steadily.
for n_built in 0..2 {
@@ -353,6 +354,14 @@ where
Some(v) => v,
};
super::check_validation_code_or_log(
&validation_code_hash,
params.para_id,
&params.relay_client,
relay_parent,
)
.await;
match collator
.collate(
&parent_header,
@@ -20,5 +20,60 @@
//! 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.
use cumulus_relay_chain_interface::RelayChainInterface;
use polkadot_primitives::{
Hash as RHash, Id as ParaId, OccupiedCoreAssumption, ValidationCodeHash,
};
pub mod basic;
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() }))
}
async fn validation_code_hash(
&self,
_: PHash,
_: ParaId,
_: OccupiedCoreAssumption,
) -> RelayChainResult<Option<ValidationCodeHash>> {
unimplemented!("Not needed for test")
}
async fn candidate_pending_availability(
&self,
_: 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(
&self,
_: PHash,
@@ -21,7 +21,7 @@ use cumulus_primitives_core::{
relay_chain::{
runtime_api::ParachainHost, Block as PBlock, BlockId, CommittedCandidateReceipt,
Hash as PHash, Header as PHeader, InboundHrmpMessage, OccupiedCoreAssumption, SessionIndex,
ValidatorId,
ValidationCodeHash, ValidatorId,
},
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(
&self,
hash: PHash,
@@ -30,7 +30,7 @@ use cumulus_primitives_core::relay_chain::BlockId;
pub use cumulus_primitives_core::{
relay_chain::{
CommittedCandidateReceipt, Hash as PHash, Header as PHeader, InboundHrmpMessage,
OccupiedCoreAssumption, SessionIndex, ValidatorId,
OccupiedCoreAssumption, SessionIndex, ValidationCodeHash, ValidatorId,
},
InboundDownwardMessage, ParaId, PersistedValidationData,
};
@@ -194,6 +194,15 @@ pub trait RelayChainInterface: Send + Sync {
relay_parent: PHash,
relevant_keys: &Vec<Vec<u8>>,
) -> 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]
@@ -301,4 +310,15 @@ where
async fn header(&self, block_id: BlockId) -> RelayChainResult<Option<PHeader>> {
(**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::{
relay_chain::{
CommittedCandidateReceipt, Hash as RelayHash, Header as RelayHeader, InboundHrmpMessage,
OccupiedCoreAssumption, SessionIndex, ValidatorId,
OccupiedCoreAssumption, SessionIndex, ValidationCodeHash, ValidatorId,
},
InboundDownwardMessage, ParaId, PersistedValidationData,
};
@@ -110,6 +110,17 @@ impl RelayChainInterface for RelayChainRpcInterface {
.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(
&self,
hash: RelayHash,
@@ -647,6 +647,20 @@ impl RelayChainRpcClient {
.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(
&self,
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) {
// spawn background task.
let bg = async move {
if let Err(e) = validate_and_make_available(params).await {
if let Error::BackgroundValidationMpsc(error) = e {
if let Err(error) = validate_and_make_available(params).await {
if let Error::BackgroundValidationMpsc(error) = error {
gum::debug!(
target: LOG_TARGET,
?candidate_hash,
?error,
"Mpsc background validation mpsc died during validation- leaf no longer active?"
);
} else {
gum::error!(
target: LOG_TARGET,
"Failed to validate and make available: {:?}",
e
?candidate_hash,
?error,
"Failed to validate and make available",
);
}
}