1mod worker;
43
44pub use crate::worker::{MiningBuild, MiningHandle, MiningMetadata};
45
46use crate::worker::UntilImportedOrTimeout;
47use codec::{Decode, Encode};
48use futures::{Future, StreamExt};
49use log::*;
50use prometheus_endpoint::Registry;
51use sc_client_api::{self, backend::AuxStore, BlockOf, BlockchainEvents};
52use sc_consensus::{
53 BasicQueue, BlockCheckParams, BlockImport, BlockImportParams, BoxBlockImport,
54 BoxJustificationImport, ForkChoiceStrategy, ImportResult, Verifier,
55};
56use sp_api::ProvideRuntimeApi;
57use sp_block_builder::BlockBuilder as BlockBuilderApi;
58use sp_blockchain::HeaderBackend;
59use sp_consensus::{
60 Environment, Error as ConsensusError, ProposeArgs, Proposer, SelectChain, SyncOracle,
61};
62use sp_consensus_pow::{Seal, TotalDifficulty, POW_ENGINE_ID};
63use sp_inherents::{CreateInherentDataProviders, InherentDataProvider};
64use sp_runtime::{
65 generic::{BlockId, Digest, DigestItem},
66 traits::{Block as BlockT, Header as HeaderT},
67};
68use std::{cmp::Ordering, marker::PhantomData, sync::Arc, time::Duration};
69
70const LOG_TARGET: &str = "pow";
71
72#[derive(Debug, thiserror::Error)]
73pub enum Error<B: BlockT> {
74 #[error("Header uses the wrong engine {0:?}")]
75 WrongEngine([u8; 4]),
76 #[error("Header {0:?} is unsealed")]
77 HeaderUnsealed(B::Hash),
78 #[error("PoW validation error: invalid seal")]
79 InvalidSeal,
80 #[error("PoW validation error: preliminary verification failed")]
81 FailedPreliminaryVerify,
82 #[error("Rejecting block too far in future")]
83 TooFarInFuture,
84 #[error("Fetching best header failed using select chain: {0}")]
85 BestHeaderSelectChain(ConsensusError),
86 #[error("Fetching best header failed: {0}")]
87 BestHeader(sp_blockchain::Error),
88 #[error("Best header does not exist")]
89 NoBestHeader,
90 #[error("Block proposing error: {0}")]
91 BlockProposingError(String),
92 #[error("Fetch best hash failed via select chain: {0}")]
93 BestHashSelectChain(ConsensusError),
94 #[error("Error with block built on {0:?}: {1}")]
95 BlockBuiltError(B::Hash, ConsensusError),
96 #[error("Creating inherents failed: {0}")]
97 CreateInherents(sp_inherents::Error),
98 #[error("Checking inherents failed: {0}")]
99 CheckInherents(sp_inherents::Error),
100 #[error(
101 "Checking inherents unknown error for identifier: {}",
102 String::from_utf8_lossy(.0)
103 )]
104 CheckInherentsUnknownError(sp_inherents::InherentIdentifier),
105 #[error("Multiple pre-runtime digests")]
106 MultiplePreRuntimeDigests,
107 #[error(transparent)]
108 Client(sp_blockchain::Error),
109 #[error(transparent)]
110 Codec(codec::Error),
111 #[error("{0}")]
112 Environment(String),
113 #[error("{0}")]
114 Runtime(String),
115 #[error("{0}")]
116 Other(String),
117}
118
119impl<B: BlockT> From<Error<B>> for String {
120 fn from(error: Error<B>) -> String {
121 error.to_string()
122 }
123}
124
125impl<B: BlockT> From<Error<B>> for ConsensusError {
126 fn from(error: Error<B>) -> ConsensusError {
127 ConsensusError::ClientImport(error.to_string())
128 }
129}
130
131pub const POW_AUX_PREFIX: [u8; 4] = *b"PoW:";
133
134fn aux_key<T: AsRef<[u8]>>(hash: &T) -> Vec<u8> {
136 POW_AUX_PREFIX.iter().chain(hash.as_ref()).copied().collect()
137}
138
139#[derive(Encode, Decode, Clone, Debug, Default)]
141pub struct PowIntermediate<Difficulty> {
142 pub difficulty: Option<Difficulty>,
144}
145
146pub static INTERMEDIATE_KEY: &[u8] = b"pow1";
148
149#[derive(Encode, Decode, Clone, Debug, Default)]
151pub struct PowAux<Difficulty> {
152 pub difficulty: Difficulty,
154 pub total_difficulty: Difficulty,
156}
157
158impl<Difficulty> PowAux<Difficulty>
159where
160 Difficulty: Decode + Default,
161{
162 pub fn read<C: AuxStore, B: BlockT>(client: &C, hash: &B::Hash) -> Result<Self, Error<B>> {
164 let key = aux_key(&hash);
165
166 match client.get_aux(&key).map_err(Error::Client)? {
167 Some(bytes) => Self::decode(&mut &bytes[..]).map_err(Error::Codec),
168 None => Ok(Self::default()),
169 }
170 }
171}
172
173pub trait PowAlgorithm<B: BlockT> {
175 type Difficulty: TotalDifficulty + Default + Encode + Decode + Ord + Clone + Copy;
177
178 fn difficulty(&self, parent: B::Hash) -> Result<Self::Difficulty, Error<B>>;
183 fn preliminary_verify(
187 &self,
188 _pre_hash: &B::Hash,
189 _seal: &Seal,
190 ) -> Result<Option<bool>, Error<B>> {
191 Ok(None)
192 }
193 fn break_tie(&self, _own_seal: &Seal, _new_seal: &Seal) -> bool {
200 false
201 }
202 fn verify(
204 &self,
205 parent: &BlockId<B>,
206 pre_hash: &B::Hash,
207 pre_digest: Option<&[u8]>,
208 seal: &Seal,
209 difficulty: Self::Difficulty,
210 ) -> Result<bool, Error<B>>;
211}
212
213pub struct PowBlockImport<B: BlockT, I, C, S, Algorithm, CIDP> {
215 algorithm: Algorithm,
216 inner: I,
217 select_chain: S,
218 client: Arc<C>,
219 create_inherent_data_providers: Arc<CIDP>,
220 check_inherents_after: <<B as BlockT>::Header as HeaderT>::Number,
221}
222
223impl<B: BlockT, I: Clone, C, S: Clone, Algorithm: Clone, CIDP> Clone
224 for PowBlockImport<B, I, C, S, Algorithm, CIDP>
225{
226 fn clone(&self) -> Self {
227 Self {
228 algorithm: self.algorithm.clone(),
229 inner: self.inner.clone(),
230 select_chain: self.select_chain.clone(),
231 client: self.client.clone(),
232 create_inherent_data_providers: self.create_inherent_data_providers.clone(),
233 check_inherents_after: self.check_inherents_after,
234 }
235 }
236}
237
238impl<B, I, C, S, Algorithm, CIDP> PowBlockImport<B, I, C, S, Algorithm, CIDP>
239where
240 B: BlockT,
241 I: BlockImport<B> + Send + Sync,
242 I::Error: Into<ConsensusError>,
243 C: ProvideRuntimeApi<B> + Send + Sync + HeaderBackend<B> + AuxStore + BlockOf,
244 C::Api: BlockBuilderApi<B>,
245 Algorithm: PowAlgorithm<B>,
246 CIDP: CreateInherentDataProviders<B, ()>,
247{
248 pub fn new(
250 inner: I,
251 client: Arc<C>,
252 algorithm: Algorithm,
253 check_inherents_after: <<B as BlockT>::Header as HeaderT>::Number,
254 select_chain: S,
255 create_inherent_data_providers: CIDP,
256 ) -> Self {
257 Self {
258 inner,
259 client,
260 algorithm,
261 check_inherents_after,
262 select_chain,
263 create_inherent_data_providers: Arc::new(create_inherent_data_providers),
264 }
265 }
266
267 async fn check_inherents(
268 &self,
269 block: B,
270 at_hash: B::Hash,
271 inherent_data_providers: CIDP::InherentDataProviders,
272 ) -> Result<(), Error<B>> {
273 use sp_block_builder::CheckInherentsError;
274
275 if *block.header().number() < self.check_inherents_after {
276 return Ok(())
277 }
278
279 sp_block_builder::check_inherents(
280 self.client.clone(),
281 at_hash,
282 block,
283 &inherent_data_providers,
284 )
285 .await
286 .map_err(|e| match e {
287 CheckInherentsError::CreateInherentData(e) => Error::CreateInherents(e),
288 CheckInherentsError::Client(e) => Error::Client(e.into()),
289 CheckInherentsError::CheckInherents(e) => Error::CheckInherents(e),
290 CheckInherentsError::CheckInherentsUnknownError(id) =>
291 Error::CheckInherentsUnknownError(id),
292 })?;
293
294 Ok(())
295 }
296}
297
298#[async_trait::async_trait]
299impl<B, I, C, S, Algorithm, CIDP> BlockImport<B> for PowBlockImport<B, I, C, S, Algorithm, CIDP>
300where
301 B: BlockT,
302 I: BlockImport<B> + Send + Sync,
303 I::Error: Into<ConsensusError>,
304 S: SelectChain<B>,
305 C: ProvideRuntimeApi<B> + Send + Sync + HeaderBackend<B> + AuxStore + BlockOf,
306 C::Api: BlockBuilderApi<B>,
307 Algorithm: PowAlgorithm<B> + Send + Sync,
308 Algorithm::Difficulty: 'static + Send,
309 CIDP: CreateInherentDataProviders<B, ()> + Send + Sync,
310{
311 type Error = ConsensusError;
312
313 async fn check_block(&self, block: BlockCheckParams<B>) -> Result<ImportResult, Self::Error> {
314 self.inner.check_block(block).await.map_err(Into::into)
315 }
316
317 async fn import_block(
318 &self,
319 mut block: BlockImportParams<B>,
320 ) -> Result<ImportResult, Self::Error> {
321 let best_header = self
322 .select_chain
323 .best_chain()
324 .await
325 .map_err(|e| format!("Fetch best chain failed via select chain: {}", e))
326 .map_err(ConsensusError::ChainLookup)?;
327 let best_hash = best_header.hash();
328
329 let parent_hash = *block.header.parent_hash();
330 let best_aux = PowAux::read::<_, B>(self.client.as_ref(), &best_hash)?;
331 let mut aux = PowAux::read::<_, B>(self.client.as_ref(), &parent_hash)?;
332
333 if let Some(inner_body) = block.body.take() {
334 let check_block = B::new(block.header.clone(), inner_body);
335
336 if !block.state_action.skip_execution_checks() {
337 self.check_inherents(
338 check_block.clone(),
339 parent_hash,
340 self.create_inherent_data_providers
341 .create_inherent_data_providers(parent_hash, ())
342 .await?,
343 )
344 .await?;
345 }
346
347 block.body = Some(check_block.deconstruct().1);
348 }
349
350 let inner_seal = fetch_seal::<B>(block.post_digests.last(), block.header.hash())?;
351
352 let intermediate = block
353 .remove_intermediate::<PowIntermediate<Algorithm::Difficulty>>(INTERMEDIATE_KEY)?;
354
355 let difficulty = match intermediate.difficulty {
356 Some(difficulty) => difficulty,
357 None => self.algorithm.difficulty(parent_hash)?,
358 };
359
360 let pre_hash = block.header.hash();
361 let pre_digest = find_pre_digest::<B>(&block.header)?;
362 if !self.algorithm.verify(
363 &BlockId::hash(parent_hash),
364 &pre_hash,
365 pre_digest.as_ref().map(|v| &v[..]),
366 &inner_seal,
367 difficulty,
368 )? {
369 return Err(Error::<B>::InvalidSeal.into())
370 }
371
372 aux.difficulty = difficulty;
373 aux.total_difficulty.increment(difficulty);
374
375 let key = aux_key(&block.post_hash());
376 block.auxiliary.push((key, Some(aux.encode())));
377 if block.fork_choice.is_none() {
378 block.fork_choice = Some(ForkChoiceStrategy::Custom(
379 match aux.total_difficulty.cmp(&best_aux.total_difficulty) {
380 Ordering::Less => false,
381 Ordering::Greater => true,
382 Ordering::Equal => {
383 let best_inner_seal =
384 fetch_seal::<B>(best_header.digest().logs.last(), best_hash)?;
385
386 self.algorithm.break_tie(&best_inner_seal, &inner_seal)
387 },
388 },
389 ));
390 }
391
392 self.inner.import_block(block).await.map_err(Into::into)
393 }
394}
395
396pub struct PowVerifier<B: BlockT, Algorithm> {
398 algorithm: Algorithm,
399 _marker: PhantomData<B>,
400}
401
402impl<B: BlockT, Algorithm> PowVerifier<B, Algorithm> {
403 pub fn new(algorithm: Algorithm) -> Self {
404 Self { algorithm, _marker: PhantomData }
405 }
406
407 fn check_header(&self, mut header: B::Header) -> Result<(B::Header, DigestItem), Error<B>>
408 where
409 Algorithm: PowAlgorithm<B>,
410 {
411 let hash = header.hash();
412
413 let (seal, inner_seal) = match header.digest_mut().pop() {
414 Some(DigestItem::Seal(id, seal)) =>
415 if id == POW_ENGINE_ID {
416 (DigestItem::Seal(id, seal.clone()), seal)
417 } else {
418 return Err(Error::WrongEngine(id))
419 },
420 _ => return Err(Error::HeaderUnsealed(hash)),
421 };
422
423 let pre_hash = header.hash();
424
425 if !self.algorithm.preliminary_verify(&pre_hash, &inner_seal)?.unwrap_or(true) {
426 return Err(Error::FailedPreliminaryVerify)
427 }
428
429 Ok((header, seal))
430 }
431}
432
433#[async_trait::async_trait]
434impl<B: BlockT, Algorithm> Verifier<B> for PowVerifier<B, Algorithm>
435where
436 Algorithm: PowAlgorithm<B> + Send + Sync,
437 Algorithm::Difficulty: 'static + Send,
438{
439 async fn verify(
440 &self,
441 mut block: BlockImportParams<B>,
442 ) -> Result<BlockImportParams<B>, String> {
443 let hash = block.header.hash();
444 let (checked_header, seal) = self.check_header(block.header)?;
445
446 let intermediate = PowIntermediate::<Algorithm::Difficulty> { difficulty: None };
447 block.header = checked_header;
448 block.post_digests.push(seal);
449 block.insert_intermediate(INTERMEDIATE_KEY, intermediate);
450 block.post_hash = Some(hash);
451
452 Ok(block)
453 }
454}
455
456pub type PowImportQueue<B> = BasicQueue<B>;
458
459pub fn import_queue<B, Algorithm>(
461 block_import: BoxBlockImport<B>,
462 justification_import: Option<BoxJustificationImport<B>>,
463 algorithm: Algorithm,
464 spawner: &impl sp_core::traits::SpawnEssentialNamed,
465 registry: Option<&Registry>,
466) -> Result<PowImportQueue<B>, sp_consensus::Error>
467where
468 B: BlockT,
469 Algorithm: PowAlgorithm<B> + Clone + Send + Sync + 'static,
470 Algorithm::Difficulty: Send,
471{
472 let verifier = PowVerifier::new(algorithm);
473
474 Ok(BasicQueue::new(verifier, block_import, justification_import, spawner, registry))
475}
476
477pub fn start_mining_worker<Block, C, S, Algorithm, E, SO, L, CIDP>(
487 block_import: BoxBlockImport<Block>,
488 client: Arc<C>,
489 select_chain: S,
490 algorithm: Algorithm,
491 mut env: E,
492 sync_oracle: SO,
493 justification_sync_link: L,
494 pre_runtime: Option<Vec<u8>>,
495 create_inherent_data_providers: CIDP,
496 timeout: Duration,
497 build_time: Duration,
498) -> (MiningHandle<Block, Algorithm, L>, impl Future<Output = ()>)
499where
500 Block: BlockT,
501 C: BlockchainEvents<Block> + 'static,
502 S: SelectChain<Block> + 'static,
503 Algorithm: PowAlgorithm<Block> + Clone,
504 Algorithm::Difficulty: Send + 'static,
505 E: Environment<Block> + Send + Sync + 'static,
506 E::Error: std::fmt::Debug,
507 E::Proposer: Proposer<Block>,
508 SO: SyncOracle + Clone + Send + Sync + 'static,
509 L: sc_consensus::JustificationSyncLink<Block>,
510 CIDP: CreateInherentDataProviders<Block, ()>,
511{
512 let mut timer = UntilImportedOrTimeout::new(client.import_notification_stream(), timeout);
513 let worker = MiningHandle::new(algorithm.clone(), block_import, justification_sync_link);
514 let worker_ret = worker.clone();
515
516 let task = async move {
517 loop {
518 if timer.next().await.is_none() {
519 break
520 }
521
522 if sync_oracle.is_major_syncing() {
523 debug!(target: LOG_TARGET, "Skipping proposal due to sync.");
524 worker.on_major_syncing();
525 continue
526 }
527
528 let best_header = match select_chain.best_chain().await {
529 Ok(x) => x,
530 Err(err) => {
531 warn!(
532 target: LOG_TARGET,
533 "Unable to pull new block for authoring. \
534 Select best chain error: {}",
535 err
536 );
537 continue
538 },
539 };
540 let best_hash = best_header.hash();
541
542 if worker.best_hash() == Some(best_hash) {
543 continue
544 }
545
546 let difficulty = match algorithm.difficulty(best_hash) {
550 Ok(x) => x,
551 Err(err) => {
552 warn!(
553 target: LOG_TARGET,
554 "Unable to propose new block for authoring. \
555 Fetch difficulty failed: {}",
556 err,
557 );
558 continue
559 },
560 };
561
562 let inherent_data_providers = match create_inherent_data_providers
563 .create_inherent_data_providers(best_hash, ())
564 .await
565 {
566 Ok(x) => x,
567 Err(err) => {
568 warn!(
569 target: LOG_TARGET,
570 "Unable to propose new block for authoring. \
571 Creating inherent data providers failed: {}",
572 err,
573 );
574 continue
575 },
576 };
577
578 let inherent_data = match inherent_data_providers.create_inherent_data().await {
579 Ok(r) => r,
580 Err(e) => {
581 warn!(
582 target: LOG_TARGET,
583 "Unable to propose new block for authoring. \
584 Creating inherent data failed: {}",
585 e,
586 );
587 continue
588 },
589 };
590
591 let mut inherent_digests = Digest::default();
592 if let Some(pre_runtime) = &pre_runtime {
593 inherent_digests.push(DigestItem::PreRuntime(POW_ENGINE_ID, pre_runtime.to_vec()));
594 }
595
596 let pre_runtime = pre_runtime.clone();
597
598 let proposer = match env.init(&best_header).await {
599 Ok(x) => x,
600 Err(err) => {
601 warn!(
602 target: LOG_TARGET,
603 "Unable to propose new block for authoring. \
604 Creating proposer failed: {:?}",
605 err,
606 );
607 continue
608 },
609 };
610
611 let propose_args = ProposeArgs {
612 inherent_data,
613 inherent_digests,
614 max_duration: build_time,
615 block_size_limit: None,
616 storage_proof_recorder: None,
617 extra_extensions: Default::default(),
618 };
619
620 let proposal = match proposer.propose(propose_args).await {
621 Ok(x) => x,
622 Err(err) => {
623 warn!(
624 target: LOG_TARGET,
625 "Unable to propose new block for authoring. \
626 Creating proposal failed: {}",
627 err,
628 );
629 continue
630 },
631 };
632
633 let build = MiningBuild::<Block, Algorithm> {
634 metadata: MiningMetadata {
635 best_hash,
636 pre_hash: proposal.block.header().hash(),
637 pre_runtime: pre_runtime.clone(),
638 difficulty,
639 },
640 proposal,
641 };
642
643 worker.on_build(build);
644 }
645 };
646
647 (worker_ret, task)
648}
649
650fn find_pre_digest<B: BlockT>(header: &B::Header) -> Result<Option<Vec<u8>>, Error<B>> {
652 let mut pre_digest: Option<_> = None;
653 for log in header.digest().logs() {
654 trace!(target: LOG_TARGET, "Checking log {:?}, looking for pre runtime digest", log);
655 match (log, pre_digest.is_some()) {
656 (DigestItem::PreRuntime(POW_ENGINE_ID, _), true) =>
657 return Err(Error::MultiplePreRuntimeDigests),
658 (DigestItem::PreRuntime(POW_ENGINE_ID, v), false) => {
659 pre_digest = Some(v.clone());
660 },
661 (_, _) => trace!(target: LOG_TARGET, "Ignoring digest not meant for us"),
662 }
663 }
664
665 Ok(pre_digest)
666}
667
668fn fetch_seal<B: BlockT>(digest: Option<&DigestItem>, hash: B::Hash) -> Result<Vec<u8>, Error<B>> {
670 match digest {
671 Some(DigestItem::Seal(id, seal)) =>
672 if id == &POW_ENGINE_ID {
673 Ok(seal.clone())
674 } else {
675 Err(Error::<B>::WrongEngine(*id))
676 },
677 _ => Err(Error::<B>::HeaderUnsealed(hash)),
678 }
679}