openmina_node_common/service/block_producer/
mod.rs

1mod vrf_evaluator;
2
3use std::sync::Arc;
4
5use ledger::proofs::{
6    block::BlockParams, generate_block_proof, provers::BlockProver,
7    transaction::debug::KimchiProofError,
8};
9use mina_p2p_messages::{
10    bigint::BigInt,
11    binprot::{self, BinProtWrite},
12    v2::{self, MinaBaseProofStableV2, ProverExtendBlockchainInputStableV2, StateHash},
13};
14use node::{
15    account::AccountSecretKey,
16    block_producer::{vrf_evaluator::VrfEvaluatorInput, BlockProducerEvent},
17    core::{channels::mpsc, constants::constraint_constants, thread},
18};
19use rsa::pkcs1::DecodeRsaPublicKey;
20
21use crate::EventSender;
22
23pub struct BlockProducerService {
24    provers: Option<BlockProver>,
25    keypair: AccountSecretKey,
26    vrf_evaluation_sender: mpsc::TrackedUnboundedSender<VrfEvaluatorInput>,
27    prove_sender: mpsc::TrackedUnboundedSender<(
28        BlockProver,
29        StateHash,
30        Box<ProverExtendBlockchainInputStableV2>,
31    )>,
32}
33
34impl BlockProducerService {
35    pub fn new(
36        keypair: AccountSecretKey,
37        vrf_evaluation_sender: mpsc::TrackedUnboundedSender<VrfEvaluatorInput>,
38        prove_sender: mpsc::TrackedUnboundedSender<(
39            BlockProver,
40            StateHash,
41            Box<ProverExtendBlockchainInputStableV2>,
42        )>,
43        provers: Option<BlockProver>,
44    ) -> Self {
45        Self {
46            provers,
47            keypair,
48            vrf_evaluation_sender,
49            prove_sender,
50        }
51    }
52
53    pub fn start(
54        event_sender: EventSender,
55        keypair: AccountSecretKey,
56        provers: Option<BlockProver>,
57    ) -> Self {
58        let (vrf_evaluation_sender, vrf_evaluation_receiver) = mpsc::unbounded_channel();
59        let (prove_sender, prove_receiver) = mpsc::unbounded_channel();
60
61        let event_sender_clone = event_sender.clone();
62        let producer_keypair = keypair.clone();
63        thread::Builder::new()
64            .name("openmina_vrf_evaluator".to_owned())
65            .spawn(move || {
66                vrf_evaluator::vrf_evaluator(
67                    event_sender_clone,
68                    vrf_evaluation_receiver,
69                    producer_keypair.into(),
70                );
71            })
72            .unwrap();
73
74        let producer_keypair = keypair.clone();
75        thread::Builder::new()
76            .name("openmina_block_prover".to_owned())
77            .spawn(move || prover_loop(producer_keypair, event_sender, prove_receiver))
78            .unwrap();
79
80        BlockProducerService::new(keypair, vrf_evaluation_sender, prove_sender, provers)
81    }
82
83    pub fn keypair(&self) -> AccountSecretKey {
84        self.keypair.clone()
85    }
86
87    pub fn vrf_pending_requests(&self) -> usize {
88        self.vrf_evaluation_sender.len()
89    }
90
91    pub fn prove_pending_requests(&self) -> usize {
92        self.prove_sender.len()
93    }
94}
95
96fn prover_loop(
97    keypair: AccountSecretKey,
98    event_sender: EventSender,
99    mut rx: mpsc::TrackedUnboundedReceiver<(
100        BlockProver,
101        StateHash,
102        Box<ProverExtendBlockchainInputStableV2>,
103    )>,
104) {
105    while let Some(msg) = rx.blocking_recv() {
106        let (provers, block_hash, mut input) = msg.0;
107        let res = prove(provers, &mut input, &keypair, false);
108        if let Err(error) = &res {
109            openmina_core::error!(message = "Block proof failed", error = format!("{error:?}"));
110            if let Err(error) = dump_failed_block_proof_input(block_hash.clone(), input, error) {
111                openmina_core::error!(
112                    message = "Failure when dumping failed block proof inputs",
113                    error = format!("{error}")
114                );
115            }
116        }
117        let res = res.map_err(|err| err.to_string());
118        let _ = event_sender.send(BlockProducerEvent::BlockProve(block_hash, res).into());
119    }
120}
121
122pub fn prove(
123    provers: BlockProver,
124    input: &mut ProverExtendBlockchainInputStableV2,
125    keypair: &AccountSecretKey,
126    only_verify_constraints: bool,
127) -> anyhow::Result<Arc<MinaBaseProofStableV2>> {
128    let height = input
129        .next_state
130        .body
131        .consensus_state
132        .blockchain_length
133        .as_u32();
134    let is_genesis = height == 1
135        || constraint_constants()
136            .fork
137            .as_ref()
138            .is_some_and(|fork| fork.blockchain_length + 1 == height);
139    if !is_genesis {
140        input.prover_state.producer_private_key = keypair.into();
141    }
142
143    let res = generate_block_proof(BlockParams {
144        input,
145        block_step_prover: &provers.block_step_prover,
146        block_wrap_prover: &provers.block_wrap_prover,
147        tx_wrap_prover: &provers.tx_wrap_prover,
148        only_verify_constraints,
149        expected_step_proof: None,
150        ocaml_wrap_witness: None,
151    });
152    res.map(|proof| MinaBaseProofStableV2((&proof).into()))
153        .map(Into::into)
154}
155
156impl node::service::BlockProducerService for crate::NodeService {
157    fn provers(&self) -> BlockProver {
158        self.block_producer
159            .as_ref()
160            .expect("provers shouldn't be needed if block producer isn't initialized")
161            .provers
162            .clone()
163            .unwrap_or_else(BlockProver::get_once_made)
164    }
165
166    fn prove(&mut self, block_hash: StateHash, input: Box<ProverExtendBlockchainInputStableV2>) {
167        if self.replayer.is_some() {
168            return;
169        }
170        let provers = self.provers();
171        let _ = self
172            .block_producer
173            .as_ref()
174            .expect("prove shouldn't be requested if block producer isn't initialized")
175            .prove_sender
176            .tracked_send((provers, block_hash, input));
177    }
178
179    fn with_producer_keypair<T>(&self, f: impl FnOnce(&AccountSecretKey) -> T) -> Option<T> {
180        Some(f(&self.block_producer.as_ref()?.keypair))
181    }
182}
183
184fn dump_failed_block_proof_input(
185    block_hash: StateHash,
186    mut input: Box<ProverExtendBlockchainInputStableV2>,
187    error: &anyhow::Error,
188) -> std::io::Result<()> {
189    use ledger::proofs::transaction::ProofError;
190    use rsa::Pkcs1v15Encrypt;
191
192    const PUBLIC_KEY: &str = "-----BEGIN RSA PUBLIC KEY-----
193MIIBCgKCAQEAqVZJX+m/xMB32rMAb9CSh9M4+TGzV037/R7yLCYuLm6VgX0HBtvE
194wC7IpZeSQKsc7gx0EVn9+u24nw7ep0TJlJb7bWolRdelnOQK0t9KMn20n8QKYPvA
1955zmUXBUI/4Hja+Nck5sErut/PAamzoUK1SeYdbsLRM70GiPALe+buSBb3qEEOgm8
1966EYqichDSd1yry2hLy/1EvKm51Va+D92/1SB1TNLFLpUJ6PuSelfYC95wJ+/g+1+
197kGqG7QLzSPjAtP/YbUponwaD+t+A0kBg0hV4hhcJOkPeA2NOi04K93bz3HuYCVRe
1981fvtAVOmYJ3s4CfRCC3SudYc8ZVvERcylwIDAQAB
199-----END RSA PUBLIC KEY-----";
200
201    #[derive(binprot::macros::BinProtWrite)]
202    struct DumpBlockProof {
203        input: Box<ProverExtendBlockchainInputStableV2>,
204        key: Vec<u8>,
205        error: Vec<u8>,
206        kimchi_error_with_context: Option<KimchiProofError>,
207    }
208
209    let producer_private_key = {
210        let mut buffer = Vec::with_capacity(1024);
211        input
212            .prover_state
213            .producer_private_key
214            .binprot_write(&mut buffer)
215            .unwrap();
216        buffer
217    };
218
219    let encrypted_producer_private_key = {
220        let mut rng = rand::thread_rng();
221        let public_key = rsa::RsaPublicKey::from_pkcs1_pem(PUBLIC_KEY).unwrap();
222        public_key
223            .encrypt(&mut rng, Pkcs1v15Encrypt, &producer_private_key)
224            .unwrap()
225    };
226
227    // IMPORTANT: Make sure that `input` doesn't leak the private key.
228    input.prover_state.producer_private_key = v2::SignatureLibPrivateKeyStableV1(BigInt::one());
229
230    let error_str = error.to_string();
231
232    let input = DumpBlockProof {
233        input,
234        key: encrypted_producer_private_key,
235        error: error_str.as_bytes().to_vec(),
236        kimchi_error_with_context: match error.downcast_ref::<ProofError>() {
237            Some(ProofError::ProvingErrorWithContext(context)) => Some(context.clone()),
238            _ => None,
239        },
240    };
241
242    let debug_dir = openmina_core::get_debug_dir();
243    let filename = debug_dir
244        .join(format!("failed_block_proof_input_{block_hash}.binprot"))
245        .to_string_lossy()
246        .to_string();
247    openmina_core::warn!(message = "Dumping failed block proof.", filename = filename);
248    std::fs::create_dir_all(&debug_dir)?;
249    let mut file = std::fs::File::create(&filename)?;
250    input.binprot_write(&mut file)?;
251    file.sync_all()?;
252    Ok(())
253}