1use 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
63const PENDING_SOLUTIONS_CHANNEL_CAPACITY: usize = 10;
65
66#[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 (!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 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#[derive(Debug, Copy, Clone)]
120pub struct NewSlotInfo {
121 pub slot: Slot,
123 pub proof_of_time: PotOutput,
125 pub solution_range: SolutionRange,
127 pub voting_solution_range: SolutionRange,
129}
130
131#[derive(Debug, Clone)]
133pub struct NewSlotNotification {
134 pub new_slot_info: NewSlotInfo,
136 pub solution_sender: mpsc::Sender<Solution<PublicKey>>,
138}
139#[derive(Debug, Clone)]
141pub struct RewardSigningNotification {
142 pub hash: H256,
144 pub public_key: PublicKey,
146 pub signature_sender: TracingUnboundedSender<RewardSignature>,
148}
149
150pub struct SubspaceSlotWorkerOptions<Block, Client, E, SO, L, BS, AS>
152where
153 Block: BlockT,
154 SO: SyncOracle + Send + Sync,
155{
156 pub client: Arc<Client>,
158 pub env: E,
160 pub block_import: BoxBlockImport<Block>,
164 pub sync_oracle: SubspaceSyncOracle<SO>,
166 pub justification_sync_link: L,
168 pub force_authoring: bool,
170 pub backoff_authoring_blocks: Option<BS>,
172 pub subspace_link: SubspaceLink<Block>,
174 pub segment_headers_store: SegmentHeadersStore<AS>,
176 pub block_proposal_slot_portion: SlotProportion,
182 pub max_block_proposal_slot_portion: Option<SlotProportion>,
185 pub telemetry: Option<TelemetryHandle>,
187 pub offchain_tx_pool_factory: OffchainTransactionPoolFactory<Block>,
191 pub pot_verifier: PotVerifier,
193}
194
195pub 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 pending_solutions: BTreeMap<Slot, mpsc::Receiver<Solution<PublicKey>>>,
218 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 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 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 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 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 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 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 self.pending_solutions
491 .retain(|&stored_slot, _solution_receiver| stored_slot >= slot);
492
493 let mut solution_receiver = self.pending_solutions.remove(&slot)?;
494 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 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_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 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 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 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 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 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
898pub(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}