sc_consensus_subspace/
slot_worker.rs

1//! Slot worker drives block and vote production based on slots produced in [`sc_proof_of_time`].
2//!
3//! While slot worker uses [`sc_consensus_slots`], it is not driven by time, but instead by Proof of
4//! Time that is produced by [`PotSourceWorker`](sc_proof_of_time::source::PotSourceWorker).
5//!
6//! Each time a new proof is found, [`PotSlotWorker::on_proof`] is called and corresponding
7//! [`SlotInfo`] notification is sent ([`SubspaceLink::new_slot_notification_stream`]) to farmers to
8//! do the audit and try to prove they have a solution without actually waiting for the response.
9//! [`ChainConstants::block_authoring_delay`](sp_consensus_subspace::ChainConstants::block_authoring_delay)
10//! slots later (when corresponding future proof arrives) all the solutions produced by farmers so
11//! far are collected and corresponding block and/or votes are produced. In case PoT chain reorg
12//! happens, outdated solutions (they are tied to proofs of time) are thrown away.
13//!
14//! Custom [`SubspaceSyncOracle`] wrapper is introduced due to Subspace-specific changes comparing
15//! to the base Substrate behavior where major syncing is assumed to not happen in case authoring is
16//! forced.
17
18use crate::archiver::SegmentHeadersStore;
19use crate::SubspaceLink;
20use futures::channel::mpsc;
21use futures::{StreamExt, TryFutureExt};
22use sc_client_api::AuxStore;
23use sc_consensus::block_import::{BlockImportParams, StateAction};
24use sc_consensus::{BoxBlockImport, JustificationSyncLink, StorageChanges};
25use sc_consensus_slots::{
26    BackoffAuthoringBlocksStrategy, SimpleSlotWorker, SlotInfo, SlotLenienceType, SlotProportion,
27};
28use sc_proof_of_time::verifier::PotVerifier;
29use sc_proof_of_time::PotSlotWorker;
30use sc_telemetry::TelemetryHandle;
31use sc_transaction_pool_api::OffchainTransactionPoolFactory;
32use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedSender};
33use schnorrkel::context::SigningContext;
34use sp_api::{ApiError, ApiExt, ProvideRuntimeApi};
35use sp_blockchain::{Error as ClientError, HeaderBackend, HeaderMetadata};
36use sp_consensus::{BlockOrigin, Environment, Error as ConsensusError, Proposer, SyncOracle};
37use sp_consensus_slots::Slot;
38use sp_consensus_subspace::digests::{
39    extract_pre_digest, CompatibleDigestItem, PreDigest, PreDigestPotInfo,
40};
41use sp_consensus_subspace::{
42    PotNextSlotInput, SignedVote, SubspaceApi, SubspaceJustification, Vote,
43};
44use sp_core::H256;
45use sp_runtime::traits::{Block as BlockT, Header, NumberFor, One, Saturating, Zero};
46use sp_runtime::{DigestItem, Justification, Justifications};
47use std::collections::BTreeMap;
48use std::future::Future;
49use std::marker::PhantomData;
50use std::pin::Pin;
51use std::sync::atomic::{AtomicBool, Ordering};
52use std::sync::Arc;
53use subspace_core_primitives::pot::{PotCheckpoints, PotOutput};
54use subspace_core_primitives::sectors::SectorId;
55use subspace_core_primitives::solutions::{RewardSignature, Solution, SolutionRange};
56use subspace_core_primitives::{BlockNumber, PublicKey, REWARD_SIGNING_CONTEXT};
57use subspace_proof_of_space::Table;
58use subspace_verification::{
59    check_reward_signature, verify_solution, PieceCheckParams, VerifySolutionParams,
60};
61use tracing::{debug, error, info, warn};
62
63/// Large enough size for any practical purposes, there shouldn't be even this many solutions.
64const PENDING_SOLUTIONS_CHANNEL_CAPACITY: usize = 10;
65
66/// Subspace sync oracle.
67///
68/// Subspace sync oracle that takes into account force authoring flag, allowing to bootstrap
69/// Subspace network from scratch due to our fork of Substrate where sync state of nodes depends on
70/// connected nodes (none of which will be synced initially). It also accounts for DSN sync, when
71/// normal Substrate sync is paused, which might happen before Substrate's internals decide there is
72/// a sync happening, but DSN sync is already in progress.
73#[derive(Debug, Clone)]
74pub struct SubspaceSyncOracle<SO>
75where
76    SO: SyncOracle + Send + Sync,
77{
78    force_authoring: bool,
79    pause_sync: Arc<AtomicBool>,
80    inner: SO,
81}
82
83impl<SO> SyncOracle for SubspaceSyncOracle<SO>
84where
85    SO: SyncOracle + Send + Sync,
86{
87    fn is_major_syncing(&self) -> bool {
88        // This allows slot worker to produce blocks even when it is offline, which according to
89        // modified Substrate fork will happen when node is offline or connected to non-synced peers
90        // (default state), it also accounts for DSN sync
91        (!self.force_authoring && self.inner.is_major_syncing())
92            || self.pause_sync.load(Ordering::Acquire)
93    }
94
95    fn is_offline(&self) -> bool {
96        self.inner.is_offline()
97    }
98}
99
100impl<SO> SubspaceSyncOracle<SO>
101where
102    SO: SyncOracle + Send + Sync,
103{
104    /// Create new instance
105    pub fn new(
106        force_authoring: bool,
107        pause_sync: Arc<AtomicBool>,
108        substrate_sync_oracle: SO,
109    ) -> Self {
110        Self {
111            force_authoring,
112            pause_sync,
113            inner: substrate_sync_oracle,
114        }
115    }
116}
117
118/// Information about new slot that just arrived
119#[derive(Debug, Copy, Clone)]
120pub struct NewSlotInfo {
121    /// Slot
122    pub slot: Slot,
123    /// The PoT output for `slot`
124    pub proof_of_time: PotOutput,
125    /// Acceptable solution range for block authoring
126    pub solution_range: SolutionRange,
127    /// Acceptable solution range for voting
128    pub voting_solution_range: SolutionRange,
129}
130
131/// New slot notification with slot information and sender for solution for the slot.
132#[derive(Debug, Clone)]
133pub struct NewSlotNotification {
134    /// New slot information.
135    pub new_slot_info: NewSlotInfo,
136    /// Sender that can be used to send solutions for the slot.
137    pub solution_sender: mpsc::Sender<Solution<PublicKey>>,
138}
139/// Notification with a hash that needs to be signed to receive reward and sender for signature.
140#[derive(Debug, Clone)]
141pub struct RewardSigningNotification {
142    /// Hash to be signed.
143    pub hash: H256,
144    /// Public key of the plot identity that should create signature.
145    pub public_key: PublicKey,
146    /// Sender that can be used to send signature for the header.
147    pub signature_sender: TracingUnboundedSender<RewardSignature>,
148}
149
150/// Parameters for [`SubspaceSlotWorker`]
151pub struct SubspaceSlotWorkerOptions<Block, Client, E, SO, L, BS, AS>
152where
153    Block: BlockT,
154    SO: SyncOracle + Send + Sync,
155{
156    /// The client to use
157    pub client: Arc<Client>,
158    /// The environment we are producing blocks for.
159    pub env: E,
160    /// The underlying block-import object to supply our produced blocks to.
161    /// This must be a `SubspaceBlockImport` or a wrapper of it, otherwise
162    /// critical consensus logic will be omitted.
163    pub block_import: BoxBlockImport<Block>,
164    /// A sync oracle
165    pub sync_oracle: SubspaceSyncOracle<SO>,
166    /// Hook into the sync module to control the justification sync process.
167    pub justification_sync_link: L,
168    /// Force authoring of blocks even if we are offline
169    pub force_authoring: bool,
170    /// Strategy and parameters for backing off block production.
171    pub backoff_authoring_blocks: Option<BS>,
172    /// The source of timestamps for relative slots
173    pub subspace_link: SubspaceLink<Block>,
174    /// Persistent storage of segment headers
175    pub segment_headers_store: SegmentHeadersStore<AS>,
176    /// The proportion of the slot dedicated to proposing.
177    ///
178    /// The block proposing will be limited to this proportion of the slot from the starting of the
179    /// slot. However, the proposing can still take longer when there is some lenience factor applied,
180    /// because there were no blocks produced for some slots.
181    pub block_proposal_slot_portion: SlotProportion,
182    /// The maximum proportion of the slot dedicated to proposing with any lenience factor applied
183    /// due to no blocks being produced.
184    pub max_block_proposal_slot_portion: Option<SlotProportion>,
185    /// Handle use to report telemetries.
186    pub telemetry: Option<TelemetryHandle>,
187    /// The offchain transaction pool factory.
188    ///
189    /// Will be used when sending equivocation reports and votes.
190    pub offchain_tx_pool_factory: OffchainTransactionPoolFactory<Block>,
191    /// Proof of time verifier
192    pub pot_verifier: PotVerifier,
193}
194
195/// Subspace slot worker responsible for block and vote production
196pub struct SubspaceSlotWorker<PosTable, Block, Client, E, SO, L, BS, AS>
197where
198    Block: BlockT,
199    SO: SyncOracle + Send + Sync,
200{
201    client: Arc<Client>,
202    block_import: BoxBlockImport<Block>,
203    env: E,
204    sync_oracle: SubspaceSyncOracle<SO>,
205    justification_sync_link: L,
206    force_authoring: bool,
207    backoff_authoring_blocks: Option<BS>,
208    subspace_link: SubspaceLink<Block>,
209    reward_signing_context: SigningContext,
210    block_proposal_slot_portion: SlotProportion,
211    max_block_proposal_slot_portion: Option<SlotProportion>,
212    telemetry: Option<TelemetryHandle>,
213    offchain_tx_pool_factory: OffchainTransactionPoolFactory<Block>,
214    segment_headers_store: SegmentHeadersStore<AS>,
215    /// Solution receivers for challenges that were sent to farmers and expected to be received
216    /// eventually
217    pending_solutions: BTreeMap<Slot, mpsc::Receiver<Solution<PublicKey>>>,
218    /// Collection of PoT slots that can be retrieved later if needed by block production
219    pot_checkpoints: BTreeMap<Slot, PotCheckpoints>,
220    pot_verifier: PotVerifier,
221    _pos_table: PhantomData<PosTable>,
222}
223
224impl<PosTable, Block, Client, E, SO, L, BS, AS> PotSlotWorker<Block>
225    for SubspaceSlotWorker<PosTable, Block, Client, E, SO, L, BS, AS>
226where
227    Block: BlockT,
228    Client: HeaderBackend<Block> + ProvideRuntimeApi<Block>,
229    Client::Api: SubspaceApi<Block, PublicKey>,
230    SO: SyncOracle + Send + Sync,
231{
232    fn on_proof(&mut self, slot: Slot, checkpoints: PotCheckpoints) {
233        // Remove checkpoints from future slots, if present they are out of date anyway
234        self.pot_checkpoints
235            .retain(|&stored_slot, _checkpoints| stored_slot < slot);
236
237        self.pot_checkpoints.insert(slot, checkpoints);
238
239        if self.sync_oracle.is_major_syncing() {
240            debug!("Skipping farming slot {slot} due to sync");
241            return;
242        }
243
244        let maybe_root_plot_public_key = self
245            .client
246            .runtime_api()
247            .root_plot_public_key(self.client.info().best_hash)
248            .ok()
249            .flatten();
250        if maybe_root_plot_public_key.is_some() && !self.force_authoring {
251            debug!(
252                "Skipping farming slot {slot} due to root public key present and force authoring \
253                not enabled"
254            );
255            return;
256        }
257
258        let proof_of_time = checkpoints.output();
259
260        // NOTE: Best hash is not necessarily going to be the parent of corresponding block, but
261        // solution range shouldn't be too far off
262        let best_hash = self.client.info().best_hash;
263        let (solution_range, voting_solution_range) =
264            match extract_solution_ranges_for_block(self.client.as_ref(), best_hash) {
265                Ok(solution_ranges) => solution_ranges,
266                Err(error) => {
267                    warn!(
268                        %slot,
269                        %best_hash,
270                        %error,
271                        "Failed to extract solution ranges for block"
272                    );
273                    return;
274                }
275            };
276
277        let new_slot_info = NewSlotInfo {
278            slot,
279            proof_of_time,
280            solution_range,
281            voting_solution_range,
282        };
283        let (solution_sender, solution_receiver) =
284            mpsc::channel(PENDING_SOLUTIONS_CHANNEL_CAPACITY);
285
286        self.subspace_link
287            .new_slot_notification_sender
288            .notify(|| NewSlotNotification {
289                new_slot_info,
290                solution_sender,
291            });
292
293        self.pending_solutions.insert(slot, solution_receiver);
294    }
295}
296
297#[async_trait::async_trait]
298impl<PosTable, Block, Client, E, Error, SO, L, BS, AS> SimpleSlotWorker<Block>
299    for SubspaceSlotWorker<PosTable, Block, Client, E, SO, L, BS, AS>
300where
301    PosTable: Table,
302    Block: BlockT,
303    Client: ProvideRuntimeApi<Block>
304        + HeaderBackend<Block>
305        + HeaderMetadata<Block, Error = ClientError>
306        + AuxStore
307        + 'static,
308    Client::Api: SubspaceApi<Block, PublicKey>,
309    E: Environment<Block, Error = Error> + Send + Sync,
310    E::Proposer: Proposer<Block, Error = Error>,
311    SO: SyncOracle + Send + Sync,
312    L: JustificationSyncLink<Block>,
313    BS: BackoffAuthoringBlocksStrategy<NumberFor<Block>> + Send + Sync,
314    Error: std::error::Error + Send + From<ConsensusError> + 'static,
315    AS: AuxStore + Send + Sync + 'static,
316    BlockNumber: From<NumberFor<Block>>,
317{
318    type BlockImport = BoxBlockImport<Block>;
319    type SyncOracle = SubspaceSyncOracle<SO>;
320    type JustificationSyncLink = L;
321    type CreateProposer =
322        Pin<Box<dyn Future<Output = Result<E::Proposer, ConsensusError>> + Send + 'static>>;
323    type Proposer = E::Proposer;
324    type Claim = (PreDigest<PublicKey>, SubspaceJustification);
325    type AuxData = ();
326
327    fn logging_target(&self) -> &'static str {
328        "subspace"
329    }
330
331    fn block_import(&mut self) -> &mut Self::BlockImport {
332        &mut self.block_import
333    }
334
335    fn aux_data(
336        &self,
337        _parent: &Block::Header,
338        _slot: Slot,
339    ) -> Result<Self::AuxData, ConsensusError> {
340        Ok(())
341    }
342
343    fn authorities_len(&self, _epoch_data: &Self::AuxData) -> Option<usize> {
344        // This function is used in `sc-consensus-slots` in order to determine whether it is
345        // possible to skip block production under certain circumstances, returning `None` or any
346        // number smaller or equal to `1` disables that functionality and we don't want that.
347        Some(2)
348    }
349
350    async fn claim_slot(
351        &mut self,
352        parent_header: &Block::Header,
353        slot: Slot,
354        _aux_data: &Self::AuxData,
355    ) -> Option<Self::Claim> {
356        let parent_pre_digest = match extract_pre_digest(parent_header) {
357            Ok(pre_digest) => pre_digest,
358            Err(error) => {
359                error!(
360                    %error,
361                    "Failed to parse pre-digest out of parent header"
362                );
363
364                return None;
365            }
366        };
367        let parent_slot = parent_pre_digest.slot();
368
369        if slot <= parent_slot {
370            debug!(
371                "Skipping claiming slot {slot} it must be higher than parent slot {parent_slot}",
372            );
373
374            return None;
375        } else {
376            debug!(%slot, "Attempting to claim slot");
377        }
378
379        let chain_constants = self.subspace_link.chain_constants();
380
381        let parent_hash = parent_header.hash();
382        let runtime_api = self.client.runtime_api();
383
384        let (solution_range, voting_solution_range) =
385            extract_solution_ranges_for_block(self.client.as_ref(), parent_hash).ok()?;
386
387        let maybe_root_plot_public_key = runtime_api.root_plot_public_key(parent_hash).ok()?;
388
389        let parent_pot_parameters = runtime_api.pot_parameters(parent_hash).ok()?;
390        let parent_future_slot = if parent_header.number().is_zero() {
391            parent_slot
392        } else {
393            parent_slot + chain_constants.block_authoring_delay()
394        };
395
396        let (proof_of_time, future_proof_of_time, pot_justification) = {
397            // Remove checkpoints from old slots we will not need anymore
398            self.pot_checkpoints
399                .retain(|&stored_slot, _checkpoints| stored_slot > parent_slot);
400
401            let proof_of_time = self.pot_checkpoints.get(&slot)?.output();
402
403            // Future slot for which proof must be available before authoring block at this slot
404            let future_slot = slot + chain_constants.block_authoring_delay();
405
406            let pot_input = if parent_header.number().is_zero() {
407                PotNextSlotInput {
408                    slot: parent_slot + Slot::from(1),
409                    slot_iterations: parent_pot_parameters.slot_iterations(),
410                    seed: self.pot_verifier.genesis_seed(),
411                }
412            } else {
413                PotNextSlotInput::derive(
414                    parent_pot_parameters.slot_iterations(),
415                    parent_slot,
416                    parent_pre_digest.pot_info().proof_of_time(),
417                    &parent_pot_parameters.next_parameters_change(),
418                )
419            };
420
421            // Ensure proof of time is valid according to parent block
422            if !self.pot_verifier.is_output_valid(
423                pot_input,
424                slot - parent_slot,
425                proof_of_time,
426                parent_pot_parameters.next_parameters_change(),
427            ) {
428                warn!(
429                    %slot,
430                    ?pot_input,
431                    ?parent_pot_parameters,
432                    "Proof of time is invalid, skipping block authoring at slot"
433                );
434                return None;
435            }
436
437            let mut checkpoints_pot_input = if parent_header.number().is_zero() {
438                PotNextSlotInput {
439                    slot: parent_slot + Slot::from(1),
440                    slot_iterations: parent_pot_parameters.slot_iterations(),
441                    seed: self.pot_verifier.genesis_seed(),
442                }
443            } else {
444                let parent_pot_info = parent_pre_digest.pot_info();
445
446                PotNextSlotInput::derive(
447                    parent_pot_parameters.slot_iterations(),
448                    parent_future_slot,
449                    parent_pot_info.future_proof_of_time(),
450                    &parent_pot_parameters.next_parameters_change(),
451                )
452            };
453            let seed = checkpoints_pot_input.seed;
454
455            let mut checkpoints = Vec::with_capacity((*future_slot - *parent_future_slot) as usize);
456
457            for slot in *parent_future_slot + 1..=*future_slot {
458                let slot = Slot::from(slot);
459                let maybe_slot_checkpoints = self.pot_verifier.get_checkpoints(
460                    checkpoints_pot_input.slot_iterations,
461                    checkpoints_pot_input.seed,
462                );
463                let Some(slot_checkpoints) = maybe_slot_checkpoints else {
464                    warn!("Proving failed during block authoring");
465                    return None;
466                };
467
468                checkpoints.push(slot_checkpoints);
469
470                checkpoints_pot_input = PotNextSlotInput::derive(
471                    checkpoints_pot_input.slot_iterations,
472                    slot,
473                    slot_checkpoints.output(),
474                    &parent_pot_parameters.next_parameters_change(),
475                );
476            }
477
478            let future_proof_of_time = checkpoints
479                .last()
480                .expect("Never empty, there is at least one slot between blocks; qed")
481                .output();
482
483            let pot_justification = SubspaceJustification::PotCheckpoints { seed, checkpoints };
484
485            (proof_of_time, future_proof_of_time, pot_justification)
486        };
487
488        let mut solution_receiver = {
489            // Remove receivers for old slots we will not need anymore
490            self.pending_solutions
491                .retain(|&stored_slot, _solution_receiver| stored_slot >= slot);
492
493            let mut solution_receiver = self.pending_solutions.remove(&slot)?;
494            // Time is out, we will not accept any more solutions
495            solution_receiver.close();
496            solution_receiver
497        };
498
499        let mut maybe_pre_digest = None;
500
501        while let Some(solution) = solution_receiver.next().await {
502            if let Some(root_plot_public_key) = &maybe_root_plot_public_key {
503                if &solution.public_key != root_plot_public_key {
504                    // Only root plot public key is allowed, no need to even try to claim block or
505                    // vote.
506                    continue;
507                }
508            }
509
510            let sector_id = SectorId::new(
511                solution.public_key.hash(),
512                solution.sector_index,
513                solution.history_size,
514            );
515
516            let history_size = runtime_api.history_size(parent_hash).ok()?;
517            let max_pieces_in_sector = runtime_api.max_pieces_in_sector(parent_hash).ok()?;
518
519            let segment_index = sector_id
520                .derive_piece_index(
521                    solution.piece_offset,
522                    solution.history_size,
523                    max_pieces_in_sector,
524                    chain_constants.recent_segments(),
525                    chain_constants.recent_history_fraction(),
526                )
527                .segment_index();
528            let maybe_segment_commitment = self
529                .segment_headers_store
530                .get_segment_header(segment_index)
531                .map(|segment_header| segment_header.segment_commitment());
532
533            let segment_commitment = match maybe_segment_commitment {
534                Some(segment_commitment) => segment_commitment,
535                None => {
536                    warn!(
537                        %slot,
538                        %segment_index,
539                        "Segment commitment not found",
540                    );
541                    continue;
542                }
543            };
544            let sector_expiration_check_segment_index = match solution
545                .history_size
546                .sector_expiration_check(chain_constants.min_sector_lifetime())
547            {
548                Some(sector_expiration_check) => sector_expiration_check.segment_index(),
549                None => {
550                    continue;
551                }
552            };
553            let sector_expiration_check_segment_commitment = runtime_api
554                .segment_commitment(parent_hash, sector_expiration_check_segment_index)
555                .ok()?;
556
557            let solution_verification_result = verify_solution::<PosTable, _>(
558                &solution,
559                slot.into(),
560                &VerifySolutionParams {
561                    proof_of_time,
562                    solution_range: voting_solution_range,
563                    piece_check_params: Some(PieceCheckParams {
564                        max_pieces_in_sector,
565                        segment_commitment,
566                        recent_segments: chain_constants.recent_segments(),
567                        recent_history_fraction: chain_constants.recent_history_fraction(),
568                        min_sector_lifetime: chain_constants.min_sector_lifetime(),
569                        current_history_size: history_size,
570                        sector_expiration_check_segment_commitment,
571                    }),
572                },
573                &self.subspace_link.kzg,
574            );
575
576            match solution_verification_result {
577                Ok(solution_distance) => {
578                    // If solution is of high enough quality and block pre-digest wasn't produced yet,
579                    // block reward is claimed
580                    if solution_distance <= solution_range / 2 {
581                        if maybe_pre_digest.is_none() {
582                            info!(%slot, "🚜 Claimed block at slot");
583                            maybe_pre_digest.replace(PreDigest::V0 {
584                                slot,
585                                solution,
586                                pot_info: PreDigestPotInfo::V0 {
587                                    proof_of_time,
588                                    future_proof_of_time,
589                                },
590                            });
591                        } else {
592                            info!(
593                                %slot,
594                                "Skipping solution that has quality sufficient for block because \
595                                block pre-digest was already created",
596                            );
597                        }
598                    } else if !parent_header.number().is_zero() {
599                        // Not sending vote on top of genesis block since segment headers since piece
600                        // verification wouldn't be possible due to missing (for now) segment commitment
601                        info!(%slot, "🗳️ Claimed vote at slot");
602
603                        self.create_vote(
604                            parent_header,
605                            slot,
606                            solution,
607                            proof_of_time,
608                            future_proof_of_time,
609                        )
610                        .await;
611                    }
612                }
613                Err(error @ subspace_verification::Error::OutsideSolutionRange { .. }) => {
614                    // Solution range might have just adjusted, but when farmer was auditing they
615                    // didn't know about this, so downgrade warning to debug message
616                    if runtime_api
617                        .solution_ranges(parent_hash)
618                        .ok()
619                        .and_then(|solution_ranges| solution_ranges.next)
620                        .is_some()
621                    {
622                        debug!(
623                            %slot,
624                            %error,
625                            "Invalid solution received",
626                        );
627                    } else {
628                        warn!(
629                            %slot,
630                            %error,
631                            "Invalid solution received",
632                        );
633                    }
634                }
635                Err(error) => {
636                    warn!(
637                        %slot,
638                        %error,
639                        "Invalid solution received",
640                    );
641                }
642            }
643        }
644
645        maybe_pre_digest.map(|pre_digest| (pre_digest, pot_justification))
646    }
647
648    fn pre_digest_data(
649        &self,
650        _slot: Slot,
651        (pre_digest, _justification): &Self::Claim,
652    ) -> Vec<DigestItem> {
653        vec![DigestItem::subspace_pre_digest(pre_digest)]
654    }
655
656    async fn block_import_params(
657        &self,
658        header: Block::Header,
659        header_hash: &Block::Hash,
660        body: Vec<Block::Extrinsic>,
661        storage_changes: sc_consensus_slots::StorageChanges<Block>,
662        (pre_digest, justification): Self::Claim,
663        _aux_data: Self::AuxData,
664    ) -> Result<BlockImportParams<Block>, ConsensusError> {
665        let signature = self
666            .sign_reward(
667                H256::from_slice(header_hash.as_ref()),
668                pre_digest.solution().public_key,
669            )
670            .await?;
671
672        let digest_item = DigestItem::subspace_seal(signature);
673
674        let mut import_block = BlockImportParams::new(BlockOrigin::Own, header);
675        import_block.post_digests.push(digest_item);
676        import_block.body = Some(body);
677        import_block.state_action =
678            StateAction::ApplyChanges(StorageChanges::Changes(storage_changes));
679        import_block
680            .justifications
681            .replace(Justifications::from(Justification::from(justification)));
682
683        Ok(import_block)
684    }
685
686    fn force_authoring(&self) -> bool {
687        self.force_authoring
688    }
689
690    fn should_backoff(&self, slot: Slot, chain_head: &Block::Header) -> bool {
691        if let Some(strategy) = &self.backoff_authoring_blocks {
692            if let Ok(chain_head_slot) = extract_pre_digest(chain_head).map(|digest| digest.slot())
693            {
694                return strategy.should_backoff(
695                    *chain_head.number(),
696                    chain_head_slot,
697                    self.client.info().finalized_number,
698                    slot,
699                    self.logging_target(),
700                );
701            }
702        }
703        false
704    }
705
706    fn sync_oracle(&mut self) -> &mut Self::SyncOracle {
707        &mut self.sync_oracle
708    }
709
710    fn justification_sync_link(&mut self) -> &mut Self::JustificationSyncLink {
711        &mut self.justification_sync_link
712    }
713
714    fn proposer(&mut self, block: &Block::Header) -> Self::CreateProposer {
715        Box::pin(
716            self.env
717                .init(block)
718                .map_err(|e| ConsensusError::ClientImport(e.to_string())),
719        )
720    }
721
722    fn telemetry(&self) -> Option<TelemetryHandle> {
723        self.telemetry.clone()
724    }
725
726    fn proposing_remaining_duration(&self, slot_info: &SlotInfo<Block>) -> std::time::Duration {
727        let parent_slot = extract_pre_digest(&slot_info.chain_head)
728            .ok()
729            .map(|d| d.slot());
730
731        sc_consensus_slots::proposing_remaining_duration(
732            parent_slot,
733            slot_info,
734            &self.block_proposal_slot_portion,
735            self.max_block_proposal_slot_portion.as_ref(),
736            SlotLenienceType::Exponential,
737            self.logging_target(),
738        )
739    }
740}
741
742impl<PosTable, Block, Client, E, Error, SO, L, BS, AS>
743    SubspaceSlotWorker<PosTable, Block, Client, E, SO, L, BS, AS>
744where
745    PosTable: Table,
746    Block: BlockT,
747    Client: ProvideRuntimeApi<Block>
748        + HeaderBackend<Block>
749        + HeaderMetadata<Block, Error = ClientError>
750        + AuxStore
751        + 'static,
752    Client::Api: SubspaceApi<Block, PublicKey>,
753    E: Environment<Block, Error = Error> + Send + Sync,
754    E::Proposer: Proposer<Block, Error = Error>,
755    SO: SyncOracle + Send + Sync,
756    L: JustificationSyncLink<Block>,
757    BS: BackoffAuthoringBlocksStrategy<NumberFor<Block>> + Send + Sync,
758    Error: std::error::Error + Send + From<ConsensusError> + 'static,
759    AS: AuxStore + Send + Sync + 'static,
760    BlockNumber: From<NumberFor<Block>>,
761{
762    /// Create new Subspace slot worker
763    pub fn new(
764        SubspaceSlotWorkerOptions {
765            client,
766            env,
767            block_import,
768            sync_oracle,
769            justification_sync_link,
770            force_authoring,
771            backoff_authoring_blocks,
772            subspace_link,
773            segment_headers_store,
774            block_proposal_slot_portion,
775            max_block_proposal_slot_portion,
776            telemetry,
777            offchain_tx_pool_factory,
778            pot_verifier,
779        }: SubspaceSlotWorkerOptions<Block, Client, E, SO, L, BS, AS>,
780    ) -> Self {
781        Self {
782            client: client.clone(),
783            block_import,
784            env,
785            sync_oracle,
786            justification_sync_link,
787            force_authoring,
788            backoff_authoring_blocks,
789            subspace_link,
790            reward_signing_context: schnorrkel::context::signing_context(REWARD_SIGNING_CONTEXT),
791            block_proposal_slot_portion,
792            max_block_proposal_slot_portion,
793            telemetry,
794            offchain_tx_pool_factory,
795            segment_headers_store,
796            pending_solutions: Default::default(),
797            pot_checkpoints: Default::default(),
798            pot_verifier,
799            _pos_table: PhantomData::<PosTable>,
800        }
801    }
802
803    async fn create_vote(
804        &self,
805        parent_header: &Block::Header,
806        slot: Slot,
807        solution: Solution<PublicKey>,
808        proof_of_time: PotOutput,
809        future_proof_of_time: PotOutput,
810    ) {
811        let parent_hash = parent_header.hash();
812        let mut runtime_api = self.client.runtime_api();
813        // Register the offchain tx pool to be able to use it from the runtime.
814        runtime_api.register_extension(
815            self.offchain_tx_pool_factory
816                .offchain_transaction_pool(parent_hash),
817        );
818
819        if self.should_backoff(slot, parent_header) {
820            return;
821        }
822
823        // Vote doesn't have extrinsics or state, hence dummy values
824        let vote = Vote::V0 {
825            height: parent_header.number().saturating_add(One::one()),
826            parent_hash: parent_header.hash(),
827            slot,
828            solution: solution.clone(),
829            proof_of_time,
830            future_proof_of_time,
831        };
832
833        let signature = match self.sign_reward(vote.hash(), solution.public_key).await {
834            Ok(signature) => signature,
835            Err(error) => {
836                error!(
837                    %slot,
838                    %error,
839                    "Failed to submit vote",
840                );
841                return;
842            }
843        };
844
845        let signed_vote = SignedVote { vote, signature };
846
847        if let Err(error) = runtime_api.submit_vote_extrinsic(parent_hash, signed_vote) {
848            error!(
849                %slot,
850                %error,
851                "Failed to submit vote",
852            );
853        }
854    }
855
856    async fn sign_reward(
857        &self,
858        hash: H256,
859        public_key: PublicKey,
860    ) -> Result<RewardSignature, ConsensusError> {
861        let (signature_sender, mut signature_receiver) =
862            tracing_unbounded("subspace_signature_signing_stream", 100);
863
864        self.subspace_link
865            .reward_signing_notification_sender
866            .notify(|| RewardSigningNotification {
867                hash,
868                public_key,
869                signature_sender,
870            });
871
872        while let Some(signature) = signature_receiver.next().await {
873            if check_reward_signature(
874                hash.as_ref(),
875                &signature,
876                &public_key,
877                &self.reward_signing_context,
878            )
879            .is_err()
880            {
881                warn!(
882                    %hash,
883                    "Received invalid signature for reward"
884                );
885                continue;
886            }
887
888            return Ok(signature);
889        }
890
891        Err(ConsensusError::CannotSign(format!(
892            "Farmer didn't sign reward. Key: {:?}",
893            public_key
894        )))
895    }
896}
897
898/// Extract solution ranges for block and votes, given ID of the parent block.
899pub(crate) fn extract_solution_ranges_for_block<Block, Client>(
900    client: &Client,
901    parent_hash: Block::Hash,
902) -> Result<(u64, u64), ApiError>
903where
904    Block: BlockT,
905    Client: ProvideRuntimeApi<Block>,
906    Client::Api: SubspaceApi<Block, PublicKey>,
907{
908    client
909        .runtime_api()
910        .solution_ranges(parent_hash)
911        .map(|solution_ranges| {
912            (
913                solution_ranges.next.unwrap_or(solution_ranges.current),
914                solution_ranges
915                    .voting_next
916                    .unwrap_or(solution_ranges.voting_current),
917            )
918        })
919}