Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit 4109e0c

Browse files
committed
introduce a collator module in the Aura crate
1 parent f70fcf1 commit 4109e0c

File tree

1 file changed

+356
-0
lines changed

1 file changed

+356
-0
lines changed
Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
// Copyright 2023 Parity Technologies (UK) Ltd.
2+
// This file is part of Cumulus.
3+
4+
// Cumulus is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
9+
// Cumulus is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
14+
// You should have received a copy of the GNU General Public License
15+
// along with Cumulus. If not, see <http://www.gnu.org/licenses/>.
16+
17+
//! The core collator logic for Aura - slot claiming, block proposing, and collation
18+
//! packaging.
19+
//!
20+
//! The [`Collator`] struct exposed here is meant to be a component of higher-level logic
21+
//! which actually manages the control flow of the collator - which slots to claim, how
22+
//! many collations to build, when to work, etc.
23+
//!
24+
//! This module also exposes some standalone functions for common operations when building
25+
//! aura-based collators.
26+
27+
use codec::{Decode, Encode};
28+
use cumulus_client_collator::service::ServiceInterface as CollatorServiceInterface;
29+
use cumulus_client_consensus_common::{ParachainBlockImportMarker, ParachainCandidate};
30+
use cumulus_client_consensus_proposer::ProposerInterface;
31+
use cumulus_primitives_core::{
32+
relay_chain::Hash as PHash, DigestItem, ParachainBlockData, PersistedValidationData,
33+
};
34+
use cumulus_primitives_parachain_inherent::ParachainInherentData;
35+
use cumulus_relay_chain_interface::RelayChainInterface;
36+
37+
use polkadot_node_primitives::{Collation, MaybeCompressedPoV};
38+
use polkadot_overseer::Handle as OverseerHandle;
39+
use polkadot_primitives::{Block as PBlock, Header as PHeader, Id as ParaId};
40+
41+
use futures::prelude::*;
42+
use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy, StateAction};
43+
use sc_consensus_aura::standalone as aura_internal;
44+
use sp_api::ProvideRuntimeApi;
45+
use sp_application_crypto::AppPublic;
46+
use sp_consensus::BlockOrigin;
47+
use sp_consensus_aura::{AuraApi, Slot, SlotDuration};
48+
use sp_core::crypto::Pair;
49+
use sp_inherents::{CreateInherentDataProviders, InherentData, InherentDataProvider};
50+
use sp_keystore::KeystorePtr;
51+
use sp_runtime::{
52+
generic::Digest,
53+
traits::{Block as BlockT, HashFor, Header as HeaderT, Member},
54+
};
55+
use sp_state_machine::StorageChanges;
56+
use sp_timestamp::Timestamp;
57+
use std::{convert::TryFrom, error::Error, fmt::Debug, hash::Hash, sync::Arc, time::Duration};
58+
59+
/// Parameters for instantiating a [`Collator`].
60+
pub struct Params<BI, CIDP, RClient, Proposer, CS> {
61+
/// A builder for inherent data builders.
62+
pub create_inherent_data_providers: CIDP,
63+
/// The block import handle.
64+
pub block_import: BI,
65+
/// An interface to the relay-chain client.
66+
pub relay_client: Arc<RClient>,
67+
/// The keystore handle used for accessing parachain key material.
68+
pub keystore: KeystorePtr,
69+
/// The identifier of the parachain within the relay-chain.
70+
pub para_id: ParaId,
71+
/// The block proposer used for building blocks.
72+
pub proposer: Proposer,
73+
/// The collator service used for bundling proposals into collations and announcing
74+
/// to the network.
75+
pub collator_service: CS,
76+
}
77+
78+
/// A utility struct for writing collation logic that makes use of Aura entirely
79+
/// or in part. See module docs for more details.
80+
pub struct Collator<Block, P, BI, CIDP, RClient, Proposer, CS> {
81+
create_inherent_data_providers: CIDP,
82+
block_import: BI,
83+
relay_client: Arc<RClient>,
84+
keystore: KeystorePtr,
85+
para_id: ParaId,
86+
proposer: Proposer,
87+
collator_service: CS,
88+
_marker: std::marker::PhantomData<(Block, P)>,
89+
}
90+
91+
impl<Block, P, BI, CIDP, RClient, Proposer, CS> Collator<Block, P, BI, CIDP, RClient, Proposer, CS>
92+
where
93+
Block: BlockT,
94+
RClient: RelayChainInterface,
95+
CIDP: CreateInherentDataProviders<Block, ()> + 'static,
96+
BI: BlockImport<Block> + ParachainBlockImportMarker + Send + Sync + 'static,
97+
Proposer: ProposerInterface<Block, Transaction = BI::Transaction>,
98+
Proposer::Transaction: Sync,
99+
CS: CollatorServiceInterface<Block>,
100+
P: Pair + Send + Sync,
101+
P::Public: AppPublic + Hash + Member + Encode + Decode,
102+
P::Signature: TryFrom<Vec<u8>> + Hash + Member + Encode + Decode,
103+
{
104+
/// Instantiate a new instance of the `Aura` manager.
105+
pub fn new(params: Params<BI, CIDP, RClient, Proposer, CS>) -> Self {
106+
Collator {
107+
create_inherent_data_providers: params.create_inherent_data_providers,
108+
block_import: params.block_import,
109+
relay_client: params.relay_client,
110+
keystore: params.keystore,
111+
para_id: params.para_id,
112+
proposer: params.proposer,
113+
collator_service: params.collator_service,
114+
_marker: std::marker::PhantomData,
115+
}
116+
}
117+
118+
/// Explicitly creates the inherent data for parachain block authoring and overrides
119+
/// the timestamp inherent data with the one provided, if any.
120+
pub async fn create_inherent_data(
121+
&self,
122+
relay_parent: PHash,
123+
validation_data: &PersistedValidationData,
124+
parent_hash: Block::Hash,
125+
timestamp: Option<Timestamp>,
126+
) -> Result<(ParachainInherentData, InherentData), Box<dyn Error>> {
127+
let paras_inherent_data = ParachainInherentData::create_at(
128+
relay_parent,
129+
&self.relay_client,
130+
validation_data,
131+
self.para_id,
132+
)
133+
.await;
134+
135+
let paras_inherent_data = match paras_inherent_data {
136+
Some(p) => p,
137+
None =>
138+
return Err(
139+
format!("Could not create paras inherent data at {:?}", relay_parent).into()
140+
),
141+
};
142+
143+
let mut other_inherent_data = self
144+
.create_inherent_data_providers
145+
.create_inherent_data_providers(parent_hash, ())
146+
.map_err(|e| e as Box<dyn Error>)
147+
.await?
148+
.create_inherent_data()
149+
.await
150+
.map_err(Box::new)?;
151+
152+
if let Some(timestamp) = timestamp {
153+
other_inherent_data.replace_data(sp_timestamp::INHERENT_IDENTIFIER, &timestamp);
154+
}
155+
156+
Ok((paras_inherent_data, other_inherent_data))
157+
}
158+
159+
/// Propose, seal, and import a block, packaging it into a collation.
160+
///
161+
/// Provide the slot to build at as well as any other necessary pre-digest logs,
162+
/// the inherent data, and the proposal duration and PoV size limits.
163+
///
164+
/// The Aura pre-digest should not be explicitly provided and is set internally.
165+
///
166+
/// This does not announce the collation to the parachain network or the relay chain.
167+
pub async fn collate(
168+
&mut self,
169+
parent_header: &Block::Header,
170+
slot_claim: &SlotClaim<P::Public>,
171+
pre_digest: impl Into<Option<Vec<DigestItem>>>,
172+
inherent_data: (ParachainInherentData, InherentData),
173+
proposal_duration: Duration,
174+
max_pov_size: usize,
175+
) -> Result<(Collation, ParachainBlockData<Block>, Block::Hash), Box<dyn Error>> {
176+
let mut digest = pre_digest.into().unwrap_or_default();
177+
digest.push(slot_claim.pre_digest.clone());
178+
179+
let proposal = self
180+
.proposer
181+
.propose(
182+
&parent_header,
183+
&inherent_data.0,
184+
inherent_data.1,
185+
Digest { logs: digest },
186+
proposal_duration,
187+
Some(max_pov_size),
188+
)
189+
.await
190+
.map_err(|e| Box::new(e))?;
191+
192+
let sealed_importable = seal::<_, _, P>(
193+
proposal.block,
194+
proposal.storage_changes,
195+
&slot_claim.author_pub,
196+
&self.keystore,
197+
)?;
198+
199+
let post_hash = sealed_importable.post_hash();
200+
let block = Block::new(
201+
sealed_importable.post_header(),
202+
sealed_importable
203+
.body
204+
.as_ref()
205+
.expect("body always created with this `propose` fn; qed")
206+
.clone(),
207+
);
208+
209+
self.block_import.import_block(sealed_importable).await?;
210+
211+
if let Some((collation, block_data)) = self.collator_service.build_collation(
212+
parent_header,
213+
post_hash,
214+
ParachainCandidate { block, proof: proposal.proof },
215+
) {
216+
tracing::info!(
217+
target: crate::LOG_TARGET,
218+
"PoV size {{ header: {}kb, extrinsics: {}kb, storage_proof: {}kb }}",
219+
block_data.header().encode().len() as f64 / 1024f64,
220+
block_data.extrinsics().encode().len() as f64 / 1024f64,
221+
block_data.storage_proof().encode().len() as f64 / 1024f64,
222+
);
223+
224+
if let MaybeCompressedPoV::Compressed(ref pov) = collation.proof_of_validity {
225+
tracing::info!(
226+
target: crate::LOG_TARGET,
227+
"Compressed PoV size: {}kb",
228+
pov.block_data.0.len() as f64 / 1024f64,
229+
);
230+
}
231+
232+
Ok((collation, block_data, post_hash))
233+
} else {
234+
Err(format!("Unable to produce collation").into())
235+
}
236+
}
237+
238+
/// Get the underlying collator service.
239+
pub fn collator_service(&self) -> &CS {
240+
&self.collator_service
241+
}
242+
}
243+
244+
/// A claim on an Aura slot.
245+
pub struct SlotClaim<Pub> {
246+
author_pub: Pub,
247+
pre_digest: DigestItem,
248+
timestamp: Timestamp,
249+
}
250+
251+
impl<Pub> SlotClaim<Pub> {
252+
/// Get the author's public key.
253+
pub fn author_pub(&self) -> &Pub {
254+
&self.author_pub
255+
}
256+
257+
/// Get the Aura pre-digest for this slot.
258+
pub fn pre_digest(&self) -> &DigestItem {
259+
&self.pre_digest
260+
}
261+
262+
/// Get the timestamp corresponding to the relay-chain slot this claim was
263+
/// generated against.
264+
pub fn timestamp(&self) -> Timestamp {
265+
self.timestamp
266+
}
267+
}
268+
269+
/// Attempt to claim a slot derived from the given relay-parent header's slot.
270+
pub async fn claim_slot<B, C, P>(
271+
client: &C,
272+
parent_hash: B::Hash,
273+
relay_parent_header: &PHeader,
274+
slot_duration: SlotDuration,
275+
relay_chain_slot_duration: SlotDuration,
276+
keystore: &KeystorePtr,
277+
) -> Result<Option<SlotClaim<P::Public>>, Box<dyn Error>>
278+
where
279+
B: BlockT,
280+
C: ProvideRuntimeApi<B> + Send + Sync + 'static,
281+
C::Api: AuraApi<B, P::Public>,
282+
P: Pair,
283+
P::Public: Encode + Decode,
284+
P::Signature: Encode + Decode,
285+
{
286+
// load authorities
287+
let authorities = client.runtime_api().authorities(parent_hash).map_err(Box::new)?;
288+
289+
// Determine the current slot and timestamp based on the relay-parent's.
290+
let (slot_now, timestamp) =
291+
match sc_consensus_babe::find_pre_digest::<PBlock>(relay_parent_header) {
292+
Ok(babe_pre_digest) => {
293+
let t =
294+
Timestamp::new(relay_chain_slot_duration.as_millis() * *babe_pre_digest.slot());
295+
let slot = Slot::from_timestamp(t, slot_duration);
296+
297+
(slot, t)
298+
},
299+
Err(_) => return Ok(None),
300+
};
301+
302+
// Try to claim the slot locally.
303+
let author_pub = {
304+
let res = aura_internal::claim_slot::<P>(slot_now, &authorities, keystore).await;
305+
match res {
306+
Some(p) => p,
307+
None => return Ok(None),
308+
}
309+
};
310+
311+
// Produce the pre-digest.
312+
let pre_digest = aura_internal::pre_digest::<P>(slot_now);
313+
314+
Ok(Some(SlotClaim { author_pub, pre_digest, timestamp }))
315+
}
316+
317+
/// Seal a block with a signature in the header.
318+
pub fn seal<B: BlockT, T, P>(
319+
pre_sealed: B,
320+
storage_changes: StorageChanges<T, HashFor<B>>,
321+
author_pub: &P::Public,
322+
keystore: &KeystorePtr,
323+
) -> Result<BlockImportParams<B, T>, Box<dyn Error>>
324+
where
325+
P: Pair,
326+
P::Signature: Encode + Decode + TryFrom<Vec<u8>>,
327+
P::Public: AppPublic,
328+
{
329+
let (pre_header, body) = pre_sealed.deconstruct();
330+
let pre_hash = pre_header.hash();
331+
let block_number = *pre_header.number();
332+
333+
// seal the block.
334+
let block_import_params = {
335+
let seal_digest =
336+
aura_internal::seal::<_, P>(&pre_hash, &author_pub, keystore).map_err(Box::new)?;
337+
let mut block_import_params = BlockImportParams::new(BlockOrigin::Own, pre_header);
338+
block_import_params.post_digests.push(seal_digest);
339+
block_import_params.body = Some(body.clone());
340+
block_import_params.state_action =
341+
StateAction::ApplyChanges(sc_consensus::StorageChanges::Changes(storage_changes));
342+
block_import_params.fork_choice = Some(ForkChoiceStrategy::LongestChain);
343+
block_import_params
344+
};
345+
let post_hash = block_import_params.post_hash();
346+
347+
tracing::info!(
348+
target: crate::LOG_TARGET,
349+
"🔖 Pre-sealed block for proposal at {}. Hash now {:?}, previously {:?}.",
350+
block_number,
351+
post_hash,
352+
pre_hash,
353+
);
354+
355+
Ok(block_import_params)
356+
}

0 commit comments

Comments
 (0)