domain_client_operator/
bundle_producer_election_solver.rs

1use 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        // Ideally, we can already cache operator signing key since we do not allow changing such key
78        // in the protocol right now. Leaving this as is since we anyway need to need to fetch operator's
79        // latest stake and this also returns the signing key with it.
80        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}