domain_client_operator/
bundle_producer_election_solver.rs1use sp_api::ProvideRuntimeApi;
2use sp_consensus_slots::Slot;
3use sp_core::ByteArray;
4use sp_core::bytes::to_hex;
5use sp_domains::bundle_producer_election::{
6 BundleProducerElectionParams, calculate_threshold, is_below_threshold, make_transcript,
7};
8use sp_domains::{
9 BundleProducerElectionApi, DomainId, OperatorId, OperatorPublicKey, ProofOfElection,
10};
11use sp_keystore::{Keystore, KeystorePtr};
12use sp_runtime::RuntimeAppPublic;
13use sp_runtime::traits::Block as BlockT;
14use std::marker::PhantomData;
15use std::sync::Arc;
16use subspace_core_primitives::pot::PotOutput;
17use subspace_runtime_primitives::Balance;
18use tracing::log;
19
20pub(super) struct BundleProducerElectionSolver<Block, CBlock, CClient> {
21 keystore: KeystorePtr,
22 consensus_client: Arc<CClient>,
23 _phantom_data: PhantomData<(Block, CBlock)>,
24}
25
26impl<Block, CBlock, CClient> Clone for BundleProducerElectionSolver<Block, CBlock, CClient> {
27 fn clone(&self) -> Self {
28 Self {
29 keystore: self.keystore.clone(),
30 consensus_client: self.consensus_client.clone(),
31 _phantom_data: self._phantom_data,
32 }
33 }
34}
35
36impl<Block, CBlock, CClient> BundleProducerElectionSolver<Block, CBlock, CClient>
37where
38 Block: BlockT,
39 CBlock: BlockT,
40 CClient: ProvideRuntimeApi<CBlock>,
41 CClient::Api: BundleProducerElectionApi<CBlock, Balance>,
42{
43 pub(super) fn new(keystore: KeystorePtr, consensus_client: Arc<CClient>) -> Self {
44 Self {
45 keystore,
46 consensus_client,
47 _phantom_data: PhantomData,
48 }
49 }
50
51 pub(super) fn solve_challenge(
52 &self,
53 slot: Slot,
54 consensus_block_hash: CBlock::Hash,
55 domain_id: DomainId,
56 operator_id: OperatorId,
57 proof_of_time: PotOutput,
58 ) -> sp_blockchain::Result<Option<(ProofOfElection, OperatorPublicKey)>> {
59 let BundleProducerElectionParams {
60 total_domain_stake,
61 bundle_slot_probability,
62 ..
63 } = match self
64 .consensus_client
65 .runtime_api()
66 .bundle_producer_election_params(consensus_block_hash, domain_id)?
67 {
68 Some(params) => params,
69 None => return Ok(None),
70 };
71
72 let global_challenge = proof_of_time
73 .derive_global_randomness()
74 .derive_global_challenge(slot.into());
75 let vrf_sign_data = make_transcript(domain_id, &global_challenge).into_sign_data();
76
77 if let Some((operator_signing_key, operator_stake)) = self
81 .consensus_client
82 .runtime_api()
83 .operator(consensus_block_hash, operator_id)?
84 {
85 if let Ok(maybe_vrf_signature) = Keystore::sr25519_vrf_sign(
86 &*self.keystore,
87 OperatorPublicKey::ID,
88 &operator_signing_key.clone().into(),
89 &vrf_sign_data,
90 ) {
91 if let Some(vrf_signature) = maybe_vrf_signature {
92 let threshold = calculate_threshold(
93 operator_stake,
94 total_domain_stake,
95 bundle_slot_probability,
96 );
97
98 if is_below_threshold(&vrf_signature.pre_output, threshold) {
99 let proof_of_election = ProofOfElection {
100 domain_id,
101 slot_number: slot.into(),
102 proof_of_time,
103 vrf_signature,
104 operator_id,
105 };
106 return Ok(Some((proof_of_election, operator_signing_key)));
107 }
108 } else {
109 log::warn!(
110 "Operator[{operator_id}]'s Signing key[{}] pair is not available in keystore.",
111 to_hex(operator_signing_key.as_slice(), false)
112 );
113 return Ok(None);
114 }
115 }
116 } else {
117 log::warn!("Operator[{operator_id}] is not registered on the Runtime",);
118 return Ok(None);
119 }
120
121 Ok(None)
122 }
123}