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