Skip to content

Commit 3a89c55

Browse files
authored
Use PostInherents to validate timestamp (#83)
* Use PostInherents to validate timestamp * fix tests * align nimbus basic collator implementation with the latest AuRa changes * handle AllowMultipleBlocksPerSlot
1 parent 50983dc commit 3a89c55

File tree

13 files changed

+311
-59
lines changed

13 files changed

+311
-59
lines changed

Cargo.lock

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

client/consensus/nimbus-consensus/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ sp-blockchain = { workspace = true }
1616
sp-consensus = { workspace = true }
1717
sp-core = { workspace = true }
1818
sp-inherents = { workspace = true }
19+
sp-timestamp = { workspace = true }
1920
sp-keystore = { workspace = true }
2021
sp-runtime = { workspace = true }
2122
sp-version = { workspace = true }

client/consensus/nimbus-consensus/src/collators.rs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,16 @@ use crate::*;
2727
use cumulus_client_collator::service::ServiceInterface as CollatorServiceInterface;
2828
use cumulus_client_consensus_common::{ParachainBlockImportMarker, ParachainCandidate};
2929
use cumulus_client_consensus_proposer::ProposerInterface;
30-
use cumulus_primitives_core::ParachainBlockData;
30+
use cumulus_primitives_core::{
31+
relay_chain::{OccupiedCoreAssumption, ValidationCodeHash},
32+
ParachainBlockData,
33+
};
3134
use cumulus_primitives_parachain_inherent::ParachainInherentData;
3235
use futures::prelude::*;
3336
use log::{debug, info};
3437
use nimbus_primitives::{CompatibleDigestItem, DigestsProvider, NimbusId, NIMBUS_KEY_ID};
3538
use polkadot_node_primitives::{Collation, MaybeCompressedPoV};
39+
use polkadot_primitives::Hash as RelayHash;
3640
use sc_consensus::{BlockImport, BlockImportParams};
3741
use sp_application_crypto::ByteArray;
3842
use sp_consensus::{BlockOrigin, Proposal};
@@ -195,3 +199,54 @@ where
195199

196200
<DigestItem as CompatibleDigestItem>::nimbus_seal(signature)
197201
}
202+
203+
/// Check the `local_validation_code_hash` against the validation code hash in the relay chain
204+
/// state.
205+
///
206+
/// If the code hashes do not match, it prints a warning.
207+
async fn check_validation_code_or_log(
208+
local_validation_code_hash: &ValidationCodeHash,
209+
para_id: ParaId,
210+
relay_client: &impl RelayChainInterface,
211+
relay_parent: RelayHash,
212+
) {
213+
let state_validation_code_hash = match relay_client
214+
.validation_code_hash(relay_parent, para_id, OccupiedCoreAssumption::Included)
215+
.await
216+
{
217+
Ok(hash) => hash,
218+
Err(error) => {
219+
tracing::debug!(
220+
target: super::LOG_TARGET,
221+
%error,
222+
?relay_parent,
223+
%para_id,
224+
"Failed to fetch validation code hash",
225+
);
226+
return;
227+
}
228+
};
229+
230+
match state_validation_code_hash {
231+
Some(state) => {
232+
if state != *local_validation_code_hash {
233+
tracing::warn!(
234+
target: super::LOG_TARGET,
235+
%para_id,
236+
?relay_parent,
237+
?local_validation_code_hash,
238+
relay_validation_code_hash = ?state,
239+
"Parachain code doesn't match validation code stored in the relay chain state.",
240+
);
241+
}
242+
}
243+
None => {
244+
tracing::warn!(
245+
target: super::LOG_TARGET,
246+
%para_id,
247+
?relay_parent,
248+
"Could not find validation code for parachain in the relay chain state.",
249+
);
250+
}
251+
}
252+
}

client/consensus/nimbus-consensus/src/collators/basic.rs

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,21 @@
1616

1717
use crate::*;
1818
use cumulus_client_collator::service::ServiceInterface as CollatorServiceInterface;
19-
use cumulus_client_consensus_common::ParachainBlockImportMarker;
19+
use cumulus_client_consensus_common::{self as consensus_common, ParachainBlockImportMarker};
2020
use cumulus_client_consensus_proposer::ProposerInterface;
2121
use cumulus_primitives_core::{
22-
relay_chain::{BlockId as RBlockId, Hash as PHash},
22+
relay_chain::{BlockId as RBlockId, Hash as PHash, ValidationCode},
2323
CollectCollationInfo, ParaId, PersistedValidationData,
2424
};
2525
use cumulus_relay_chain_interface::{OverseerHandle, RelayChainInterface};
2626
use futures::prelude::*;
2727
use nimbus_primitives::{DigestsProvider, NimbusApi, NimbusId};
2828
use polkadot_node_primitives::CollationResult;
2929
use polkadot_primitives::CollatorPair;
30-
use sc_client_api::{BlockBackend, BlockOf};
31-
use sp_api::ProvideRuntimeApi;
30+
use sc_client_api::{AuxStore, BlockBackend, BlockOf, StateBackend};
31+
use sp_api::{CallApiAt, ProvideRuntimeApi};
3232
use sp_blockchain::HeaderBackend;
33+
use sp_consensus_slots::{Slot, SlotDuration};
3334
use sp_core::Decode;
3435
use sp_inherents::CreateInherentDataProviders;
3536
use sp_keystore::KeystorePtr;
@@ -44,6 +45,11 @@ pub struct Params<Proposer, BI, ParaClient, RClient, CIDP, CS, ADP = ()> {
4445
pub para_id: ParaId,
4546
/// A handle to the relay-chain client's "Overseer" or task orchestrator.
4647
pub overseer_handle: OverseerHandle,
48+
/// The length of slots in the relay chain.
49+
pub relay_chain_slot_duration: Duration,
50+
/// The length of slots in this parachain.
51+
/// If the parachain doesn't have slot and rely only on relay slots, set it to None.
52+
pub slot_duration: Option<SlotDuration>,
4753
/// The underlying block proposer this should call into.
4854
pub proposer: Proposer,
4955
/// The block import handle.
@@ -79,8 +85,10 @@ pub async fn run<Block, BI, CIDP, Backend, Client, RClient, Proposer, CS, ADP>(
7985
BI: BlockImport<Block> + ParachainBlockImportMarker + Send + Sync + 'static,
8086
Client: ProvideRuntimeApi<Block>
8187
+ BlockOf
88+
+ AuxStore
8289
+ HeaderBackend<Block>
8390
+ BlockBackend<Block>
91+
+ CallApiAt<Block>
8492
+ Send
8593
+ Sync
8694
+ 'static,
@@ -112,6 +120,9 @@ pub async fn run<Block, BI, CIDP, Backend, Client, RClient, Proposer, CS, ADP>(
112120
..
113121
} = params;
114122

123+
let mut last_processed_slot = 0;
124+
let mut last_relay_chain_block = Default::default();
125+
115126
while let Some(request) = collation_requests.next().await {
116127
macro_rules! reject_with_error {
117128
($err:expr) => {{
@@ -142,6 +153,25 @@ pub async fn run<Block, BI, CIDP, Backend, Client, RClient, Proposer, CS, ADP>(
142153
continue;
143154
}
144155

156+
let Ok(Some(code)) = para_client
157+
.state_at(parent_hash)
158+
.map_err(drop)
159+
.and_then(|s| {
160+
s.storage(&sp_core::storage::well_known_keys::CODE)
161+
.map_err(drop)
162+
})
163+
else {
164+
continue;
165+
};
166+
167+
super::check_validation_code_or_log(
168+
&ValidationCode::from(code).hash(),
169+
para_id,
170+
&relay_client,
171+
*request.relay_parent(),
172+
)
173+
.await;
174+
145175
let relay_parent_header = match relay_client
146176
.header(RBlockId::hash(*request.relay_parent()))
147177
.await
@@ -165,6 +195,54 @@ pub async fn run<Block, BI, CIDP, Backend, Client, RClient, Proposer, CS, ADP>(
165195
Err(e) => reject_with_error!(e),
166196
};
167197

198+
// Determine which is the current slot
199+
let (slot_now, timestamp) = match consensus_common::relay_slot_and_timestamp(
200+
&relay_parent_header,
201+
params.relay_chain_slot_duration,
202+
) {
203+
None => {
204+
tracing::trace!(
205+
target: crate::LOG_TARGET,
206+
relay_parent = ?relay_parent_header,
207+
relay_chain_slot_duration = ?params.relay_chain_slot_duration,
208+
"Fail to get the relay slot for this relay block!"
209+
);
210+
continue;
211+
}
212+
Some((relay_slot, relay_timestamp)) => {
213+
let our_slot = if let Some(slot_duration) = params.slot_duration {
214+
Slot::from_timestamp(relay_timestamp, slot_duration)
215+
} else {
216+
// If there is no slot duration, we assume that the parachain use the relay slot directly
217+
relay_slot
218+
};
219+
tracing::debug!(
220+
target: crate::LOG_TARGET,
221+
?relay_slot,
222+
para_slot = ?our_slot,
223+
?relay_timestamp,
224+
slot_duration = ?params.slot_duration,
225+
relay_chain_slot_duration = ?params.relay_chain_slot_duration,
226+
"Adjusted relay-chain slot to parachain slot"
227+
);
228+
(our_slot, relay_timestamp)
229+
}
230+
};
231+
232+
// With async backing this function will be called every relay chain block.
233+
//
234+
// Most parachains currently run with 12 seconds slots and thus, they would try to
235+
// produce multiple blocks per slot which very likely would fail on chain. Thus, we have
236+
// this "hack" to only produce one block per slot per relay chain fork.
237+
//
238+
// With https://github.com/paritytech/polkadot-sdk/issues/3168 this implementation will be
239+
// obsolete and also the underlying issue will be fixed.
240+
if last_processed_slot >= *slot_now
241+
&& last_relay_chain_block < *relay_parent_header.number()
242+
{
243+
continue;
244+
}
245+
168246
let inherent_data = try_request!(
169247
create_inherent_data(
170248
&create_inherent_data_providers,
@@ -174,6 +252,7 @@ pub async fn run<Block, BI, CIDP, Backend, Client, RClient, Proposer, CS, ADP>(
174252
&relay_client,
175253
*request.relay_parent(),
176254
nimbus_id.clone(),
255+
Some(timestamp)
177256
)
178257
.await
179258
);
@@ -216,5 +295,8 @@ pub async fn run<Block, BI, CIDP, Backend, Client, RClient, Proposer, CS, ADP>(
216295
request.complete(None);
217296
tracing::debug!(target: crate::LOG_TARGET, "No block proposal");
218297
}
298+
299+
last_processed_slot = *slot_now;
300+
last_relay_chain_block = *relay_parent_header.number();
219301
}
220302
}

client/consensus/nimbus-consensus/src/collators/lookahead.rs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ where
207207
};
208208

209209
// Determine which is the current slot
210-
let slot_now = match consensus_common::relay_slot_and_timestamp(
210+
let (slot_now, timestamp) = match consensus_common::relay_slot_and_timestamp(
211211
&relay_parent_header,
212212
params.relay_chain_slot_duration,
213213
) {
@@ -236,7 +236,7 @@ where
236236
relay_chain_slot_duration = ?params.relay_chain_slot_duration,
237237
"Adjusted relay-chain slot to parachain slot"
238238
);
239-
our_slot
239+
(our_slot, relay_timestamp)
240240
}
241241
};
242242

@@ -343,6 +343,7 @@ where
343343
&params.relay_client,
344344
relay_parent,
345345
author_id.clone(),
346+
Some(timestamp),
346347
)
347348
.await
348349
{
@@ -355,19 +356,21 @@ where
355356

356357
// Compute the hash of the parachain runtime bytecode that we using to build the block.
357358
// The hash will be send to relay validators alongside the candidate.
358-
let validation_code_hash = match params.code_hash_provider.code_hash_at(parent_hash)
359-
{
360-
None => {
361-
tracing::error!(
362-
target: crate::LOG_TARGET,
363-
?parent_hash,
364-
"Could not fetch validation code hash"
365-
);
366-
break;
367-
}
368-
Some(validation_code_hash) => validation_code_hash,
359+
let Some(validation_code_hash) =
360+
params.code_hash_provider.code_hash_at(parent_hash)
361+
else {
362+
tracing::error!(target: crate::LOG_TARGET, ?parent_hash, "Could not fetch validation code hash");
363+
break;
369364
};
370365

366+
super::check_validation_code_or_log(
367+
&validation_code_hash,
368+
params.para_id,
369+
&params.relay_client,
370+
relay_parent,
371+
)
372+
.await;
373+
371374
let allowed_pov_size = {
372375
// Cap the percentage at 85% (see https://github.com/paritytech/polkadot-sdk/issues/6020)
373376
let capped_percentage = params.max_pov_percentage.min(85);

client/consensus/nimbus-consensus/src/lib.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ where
9898
}
9999
}
100100

101-
/// Explicitly creates the inherent data for parachain block authoring.
101+
/// Explicitly creates the inherent data for parachain block authoring and overrides
102+
/// the timestamp inherent data with the one provided, if any.
102103
pub(crate) async fn create_inherent_data<Block, CIDP, RClient>(
103104
create_inherent_data_providers: &CIDP,
104105
para_id: ParaId,
@@ -107,6 +108,7 @@ pub(crate) async fn create_inherent_data<Block, CIDP, RClient>(
107108
relay_client: &RClient,
108109
relay_parent: PHash,
109110
author_id: NimbusId,
111+
timestamp: impl Into<Option<sp_timestamp::Timestamp>>,
110112
) -> Result<(ParachainInherentData, InherentData), Box<dyn Error + Send + Sync + 'static>>
111113
where
112114
Block: BlockT,
@@ -131,14 +133,18 @@ where
131133
}
132134
};
133135

134-
let other_inherent_data = create_inherent_data_providers
136+
let mut other_inherent_data = create_inherent_data_providers
135137
.create_inherent_data_providers(parent, (relay_parent, validation_data.clone(), author_id))
136138
.map_err(|e| e as Box<dyn Error + Send + Sync + 'static>)
137139
.await?
138140
.create_inherent_data()
139141
.await
140142
.map_err(Box::new)?;
141143

144+
if let Some(timestamp) = timestamp.into() {
145+
other_inherent_data.replace_data(sp_timestamp::INHERENT_IDENTIFIER, &timestamp);
146+
}
147+
142148
Ok((paras_inherent_data, other_inherent_data))
143149
}
144150

0 commit comments

Comments
 (0)