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
295 Ok(())
296 }
297}
298
299#[async_trait::async_trait]
300impl<B, I, C, S, Algorithm, CIDP> BlockImport<B> for PowBlockImport<B, I, C, S, Algorithm, CIDP>
301where
302 B: BlockT,
303 I: BlockImport<B> + Send + Sync,
304 I::Error: Into<ConsensusError>,
305 S: SelectChain<B>,
306 C: ProvideRuntimeApi<B> + Send + Sync + HeaderBackend<B> + AuxStore + BlockOf,
307 C::Api: BlockBuilderApi<B>,
308 Algorithm: PowAlgorithm<B> + Send + Sync,
309 Algorithm::Difficulty: 'static + Send,
310 CIDP: CreateInherentDataProviders<B, ()> + Send + Sync,
311{
312 type Error = ConsensusError;
313
314 async fn check_block(&self, block: BlockCheckParams<B>) -> Result<ImportResult, Self::Error> {
315 self.inner.check_block(block).await.map_err(Into::into)
316 }
317
318 async fn import_block(
319 &self,
320 mut block: BlockImportParams<B>,
321 ) -> Result<ImportResult, Self::Error> {
322 let best_header = self
323 .select_chain
324 .best_chain()
325 .await
326 .map_err(|e| format!("Fetch best chain failed via select chain: {}", e))
327 .map_err(ConsensusError::ChainLookup)?;
328 let best_hash = best_header.hash();
329
330 let parent_hash = *block.header.parent_hash();
331 let best_aux = PowAux::read::<_, B>(self.client.as_ref(), &best_hash)?;
332 let mut aux = PowAux::read::<_, B>(self.client.as_ref(), &parent_hash)?;
333
334 if let Some(inner_body) = block.body.take() {
335 let check_block = B::new(block.header.clone(), inner_body);
336
337 if !block.state_action.skip_execution_checks() {
338 self.check_inherents(
339 check_block.clone(),
340 parent_hash,
341 self.create_inherent_data_providers
342 .create_inherent_data_providers(parent_hash, ())
343 .await?,
344 )
345 .await?;
346 }
347
348 block.body = Some(check_block.deconstruct().1);
349 }
350
351 let inner_seal = fetch_seal::<B>(block.post_digests.last(), block.header.hash())?;
352
353 let intermediate = block
354 .remove_intermediate::<PowIntermediate<Algorithm::Difficulty>>(INTERMEDIATE_KEY)?;
355
356 let difficulty = match intermediate.difficulty {
357 Some(difficulty) => difficulty,
358 None => self.algorithm.difficulty(parent_hash)?,
359 };
360
361 let pre_hash = block.header.hash();
362 let pre_digest = find_pre_digest::<B>(&block.header)?;
363 if !self.algorithm.verify(
364 &BlockId::hash(parent_hash),
365 &pre_hash,
366 pre_digest.as_ref().map(|v| &v[..]),
367 &inner_seal,
368 difficulty,
369 )? {
370 return Err(Error::<B>::InvalidSeal.into());
371 }
372
373 aux.difficulty = difficulty;
374 aux.total_difficulty.increment(difficulty);
375
376 let key = aux_key(&block.post_hash());
377 block.auxiliary.push((key, Some(aux.encode())));
378 if block.fork_choice.is_none() {
379 block.fork_choice = Some(ForkChoiceStrategy::Custom(
380 match aux.total_difficulty.cmp(&best_aux.total_difficulty) {
381 Ordering::Less => false,
382 Ordering::Greater => true,
383 Ordering::Equal => {
384 let best_inner_seal =
385 fetch_seal::<B>(best_header.digest().logs.last(), best_hash)?;
386
387 self.algorithm.break_tie(&best_inner_seal, &inner_seal)
388 },
389 },
390 ));
391 }
392
393 self.inner.import_block(block).await.map_err(Into::into)
394 }
395}
396
397pub struct PowVerifier<B: BlockT, Algorithm> {
399 algorithm: Algorithm,
400 _marker: PhantomData<B>,
401}
402
403impl<B: BlockT, Algorithm> PowVerifier<B, Algorithm> {
404 pub fn new(algorithm: Algorithm) -> Self {
405 Self { algorithm, _marker: PhantomData }
406 }
407
408 fn check_header(&self, mut header: B::Header) -> Result<(B::Header, DigestItem), Error<B>>
409 where
410 Algorithm: PowAlgorithm<B>,
411 {
412 let hash = header.hash();
413
414 let (seal, inner_seal) = match header.digest_mut().pop() {
415 Some(DigestItem::Seal(id, seal)) => {
416 if id == POW_ENGINE_ID {
417 (DigestItem::Seal(id, seal.clone()), seal)
418 } else {
419 return Err(Error::WrongEngine(id));
420 }
421 },
422 _ => return Err(Error::HeaderUnsealed(hash)),
423 };
424
425 let pre_hash = header.hash();
426
427 if !self.algorithm.preliminary_verify(&pre_hash, &inner_seal)?.unwrap_or(true) {
428 return Err(Error::FailedPreliminaryVerify);
429 }
430
431 Ok((header, seal))
432 }
433}
434
435#[async_trait::async_trait]
436impl<B: BlockT, Algorithm> Verifier<B> for PowVerifier<B, Algorithm>
437where
438 Algorithm: PowAlgorithm<B> + Send + Sync,
439 Algorithm::Difficulty: 'static + Send,
440{
441 async fn verify(
442 &self,
443 mut block: BlockImportParams<B>,
444 ) -> Result<BlockImportParams<B>, String> {
445 let hash = block.header.hash();
446 let (checked_header, seal) = self.check_header(block.header)?;
447
448 let intermediate = PowIntermediate::<Algorithm::Difficulty> { difficulty: None };
449 block.header = checked_header;
450 block.post_digests.push(seal);
451 block.insert_intermediate(INTERMEDIATE_KEY, intermediate);
452 block.post_hash = Some(hash);
453
454 Ok(block)
455 }
456}
457
458pub type PowImportQueue<B> = BasicQueue<B>;
460
461pub fn import_queue<B, Algorithm>(
463 block_import: BoxBlockImport<B>,
464 justification_import: Option<BoxJustificationImport<B>>,
465 algorithm: Algorithm,
466 spawner: &impl sp_core::traits::SpawnEssentialNamed,
467 registry: Option<&Registry>,
468) -> Result<PowImportQueue<B>, sp_consensus::Error>
469where
470 B: BlockT,
471 Algorithm: PowAlgorithm<B> + Clone + Send + Sync + 'static,
472 Algorithm::Difficulty: Send,
473{
474 let verifier = PowVerifier::new(algorithm);
475
476 Ok(BasicQueue::new(verifier, block_import, justification_import, spawner, registry))
477}
478
479pub fn start_mining_worker<Block, C, S, Algorithm, E, SO, L, CIDP>(
489 block_import: BoxBlockImport<Block>,
490 client: Arc<C>,
491 select_chain: S,
492 algorithm: Algorithm,
493 mut env: E,
494 sync_oracle: SO,
495 justification_sync_link: L,
496 pre_runtime: Option<Vec<u8>>,
497 create_inherent_data_providers: CIDP,
498 timeout: Duration,
499 build_time: Duration,
500) -> (MiningHandle<Block, Algorithm, L>, impl Future<Output = ()>)
501where
502 Block: BlockT,
503 C: BlockchainEvents<Block> + 'static,
504 S: SelectChain<Block> + 'static,
505 Algorithm: PowAlgorithm<Block> + Clone,
506 Algorithm::Difficulty: Send + 'static,
507 E: Environment<Block> + Send + Sync + 'static,
508 E::Error: std::fmt::Debug,
509 E::Proposer: Proposer<Block>,
510 SO: SyncOracle + Clone + Send + Sync + 'static,
511 L: sc_consensus::JustificationSyncLink<Block>,
512 CIDP: CreateInherentDataProviders<Block, ()>,
513{
514 let mut timer = UntilImportedOrTimeout::new(client.import_notification_stream(), timeout);
515 let worker = MiningHandle::new(algorithm.clone(), block_import, justification_sync_link);
516 let worker_ret = worker.clone();
517
518 let task = async move {
519 loop {
520 if timer.next().await.is_none() {
521 break;
522 }
523
524 if sync_oracle.is_major_syncing() {
525 debug!(target: LOG_TARGET, "Skipping proposal due to sync.");
526 worker.on_major_syncing();
527 continue;
528 }
529
530 let best_header = match select_chain.best_chain().await {
531 Ok(x) => x,
532 Err(err) => {
533 warn!(
534 target: LOG_TARGET,
535 "Unable to pull new block for authoring. \
536 Select best chain error: {}",
537 err
538 );
539 continue;
540 },
541 };
542 let best_hash = best_header.hash();
543
544 if worker.best_hash() == Some(best_hash) {
545 continue;
546 }
547
548 let difficulty = match algorithm.difficulty(best_hash) {
552 Ok(x) => x,
553 Err(err) => {
554 warn!(
555 target: LOG_TARGET,
556 "Unable to propose new block for authoring. \
557 Fetch difficulty failed: {}",
558 err,
559 );
560 continue;
561 },
562 };
563
564 let inherent_data_providers = match create_inherent_data_providers
565 .create_inherent_data_providers(best_hash, ())
566 .await
567 {
568 Ok(x) => x,
569 Err(err) => {
570 warn!(
571 target: LOG_TARGET,
572 "Unable to propose new block for authoring. \
573 Creating inherent data providers failed: {}",
574 err,
575 );
576 continue;
577 },
578 };
579
580 let inherent_data = match inherent_data_providers.create_inherent_data().await {
581 Ok(r) => r,
582 Err(e) => {
583 warn!(
584 target: LOG_TARGET,
585 "Unable to propose new block for authoring. \
586 Creating inherent data failed: {}",
587 e,
588 );
589 continue;
590 },
591 };
592
593 let mut inherent_digests = Digest::default();
594 if let Some(pre_runtime) = &pre_runtime {
595 inherent_digests.push(DigestItem::PreRuntime(POW_ENGINE_ID, pre_runtime.to_vec()));
596 }
597
598 let pre_runtime = pre_runtime.clone();
599
600 let proposer = match env.init(&best_header).await {
601 Ok(x) => x,
602 Err(err) => {
603 warn!(
604 target: LOG_TARGET,
605 "Unable to propose new block for authoring. \
606 Creating proposer failed: {:?}",
607 err,
608 );
609 continue;
610 },
611 };
612
613 let propose_args = ProposeArgs {
614 inherent_data,
615 inherent_digests,
616 max_duration: build_time,
617 block_size_limit: None,
618 storage_proof_recorder: None,
619 extra_extensions: Default::default(),
620 };
621
622 let proposal = match proposer.propose(propose_args).await {
623 Ok(x) => x,
624 Err(err) => {
625 warn!(
626 target: LOG_TARGET,
627 "Unable to propose new block for authoring. \
628 Creating proposal failed: {}",
629 err,
630 );
631 continue;
632 },
633 };
634
635 let build = MiningBuild::<Block, Algorithm> {
636 metadata: MiningMetadata {
637 best_hash,
638 pre_hash: proposal.block.header().hash(),
639 pre_runtime: pre_runtime.clone(),
640 difficulty,
641 },
642 proposal,
643 };
644
645 worker.on_build(build);
646 }
647 };
648
649 (worker_ret, task)
650}
651
652fn find_pre_digest<B: BlockT>(header: &B::Header) -> Result<Option<Vec<u8>>, Error<B>> {
654 let mut pre_digest: Option<_> = None;
655 for log in header.digest().logs() {
656 trace!(target: LOG_TARGET, "Checking log {:?}, looking for pre runtime digest", log);
657 match (log, pre_digest.is_some()) {
658 (DigestItem::PreRuntime(POW_ENGINE_ID, _), true) => {
659 return Err(Error::MultiplePreRuntimeDigests)
660 },
661 (DigestItem::PreRuntime(POW_ENGINE_ID, v), false) => {
662 pre_digest = Some(v.clone());
663 },
664 (_, _) => trace!(target: LOG_TARGET, "Ignoring digest not meant for us"),
665 }
666 }
667
668 Ok(pre_digest)
669}
670
671fn fetch_seal<B: BlockT>(digest: Option<&DigestItem>, hash: B::Hash) -> Result<Vec<u8>, Error<B>> {
673 match digest {
674 Some(DigestItem::Seal(id, seal)) => {
675 if id == &POW_ENGINE_ID {
676 Ok(seal.clone())
677 } else {
678 Err(Error::<B>::WrongEngine(*id))
679 }
680 },
681 _ => Err(Error::<B>::HeaderUnsealed(hash)),
682 }
683}