openmina_node_common/service/block_producer/
mod.rs1mod 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 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}