1use std::{
6 fs::File,
7 io::{BufRead, BufReader, Read},
8 net::IpAddr,
9 path::Path,
10 sync::Arc,
11 time::Duration,
12};
13
14use anyhow::Context;
15use ledger::proofs::provers::BlockProver;
16use mina_core::{consensus::ConsensusConstants, constants::constraint_constants};
17use mina_node_common::{archive::config::ArchiveStorageOptions, p2p::TaskSpawner};
18use mina_p2p_messages::v2::{self, NonZeroCurvePoint};
19use node::{
20 account::AccountSecretKey,
21 daemon_json::Daemon,
22 p2p::{
23 channels::ChannelId, connection::outgoing::P2pConnectionOutgoingInitOpts,
24 identity::SecretKey as P2pSecretKey, P2pLimits, P2pMeshsubConfig, P2pTimeouts,
25 },
26 service::Recorder,
27 snark::{get_srs, BlockVerifier, TransactionVerifier, VerifierSRS},
28 transition_frontier::{archive::archive_config::ArchiveConfig, genesis::GenesisConfig},
29 BlockProducerConfig, GlobalConfig, LedgerConfig, P2pConfig, SnarkConfig, SnarkerConfig,
30 SnarkerStrategy, TransitionFrontierConfig,
31};
32use rand::Rng;
33
34use crate::NodeServiceBuilder;
35
36use super::Node;
37
38pub struct NodeBuilder {
40 rng_seed: [u8; 32],
43 custom_initial_time: Option<redux::Timestamp>,
45 genesis_config: Arc<GenesisConfig>,
47 p2p: P2pConfig,
49 p2p_sec_key: Option<P2pSecretKey>,
51 p2p_is_seed: bool,
53 p2p_is_started: bool,
55 block_producer: Option<BlockProducerConfig>,
57 archive: Option<ArchiveConfig>,
59 snarker: Option<SnarkerConfig>,
61 service: NodeServiceBuilder,
63 verifier_srs: Option<Arc<VerifierSRS>>,
65 block_verifier_index: Option<BlockVerifier>,
67 work_verifier_index: Option<TransactionVerifier>,
69 http_port: Option<u16>,
71 daemon_conf: Daemon,
73}
74
75impl NodeBuilder {
76 pub fn new(
77 custom_rng_seed: Option<[u8; 32]>,
78 daemon_conf: Daemon,
79 genesis_config: Arc<GenesisConfig>,
80 ) -> Self {
81 let rng_seed = custom_rng_seed.unwrap_or_else(|| {
82 let mut seed = [0; 32];
83 getrandom::getrandom(&mut seed).unwrap_or_else(|_| {
84 seed = rand::thread_rng().gen();
85 });
86 seed
87 });
88 Self {
89 rng_seed,
90 custom_initial_time: None,
91 genesis_config,
92 p2p: P2pConfig {
93 libp2p_port: None,
94 listen_port: None,
95 identity_pub_key: P2pSecretKey::deterministic(0).public_key(),
97 initial_peers: Vec::new(),
98 external_addrs: Vec::new(),
99 enabled_channels: ChannelId::iter_all().collect(),
100 peer_discovery: true,
101 meshsub: P2pMeshsubConfig {
102 initial_time: Duration::ZERO,
103 ..Default::default()
104 },
105 timeouts: P2pTimeouts::default(),
106 limits: P2pLimits::default().with_max_peers(Some(100)),
107 },
108 p2p_sec_key: None,
109 p2p_is_seed: false,
110 p2p_is_started: false,
111 block_producer: None,
112 archive: None,
113 snarker: None,
114 service: NodeServiceBuilder::new(rng_seed),
115 verifier_srs: None,
116 block_verifier_index: None,
117 work_verifier_index: None,
118 http_port: None,
119 daemon_conf,
120 }
121 }
122
123 pub fn custom_initial_time(&mut self, time: redux::Timestamp) -> &mut Self {
125 self.custom_initial_time = Some(time);
126 self
127 }
128
129 pub fn p2p_sec_key(&mut self, key: P2pSecretKey) -> &mut Self {
131 self.p2p.identity_pub_key = key.public_key();
132 self.p2p_sec_key = Some(key);
133 self
134 }
135
136 pub fn p2p_libp2p_port(&mut self, port: u16) -> &mut Self {
137 self.p2p.libp2p_port = Some(port);
138 self
139 }
140
141 pub fn p2p_seed_node(&mut self) -> &mut Self {
143 self.p2p_is_seed = true;
144 self
145 }
146
147 pub fn p2p_no_discovery(&mut self) -> &mut Self {
148 self.p2p.peer_discovery = false;
149 self
150 }
151
152 pub fn initial_peers(
154 &mut self,
155 peers: impl IntoIterator<Item = P2pConnectionOutgoingInitOpts>,
156 ) -> &mut Self {
157 self.p2p.initial_peers.extend(peers);
158 self
159 }
160
161 pub fn external_addrs(&mut self, v: impl Iterator<Item = IpAddr>) -> &mut Self {
162 self.p2p.external_addrs.extend(v);
163 self
164 }
165
166 pub fn initial_peers_from_file(&mut self, path: impl AsRef<Path>) -> anyhow::Result<&mut Self> {
168 peers_from_reader(
169 &mut self.p2p.initial_peers,
170 File::open(&path).context(anyhow::anyhow!(
171 "opening peer list file {:?}",
172 path.as_ref()
173 ))?,
174 )
175 .context(anyhow::anyhow!(
176 "reading peer list file {:?}",
177 path.as_ref()
178 ))?;
179
180 Ok(self)
181 }
182
183 pub fn initial_peers_from_url(
185 &mut self,
186 url: impl reqwest::IntoUrl,
187 ) -> anyhow::Result<&mut Self> {
188 let url = url.into_url().context("failed to parse peers url")?;
189 peers_from_reader(
190 &mut self.p2p.initial_peers,
191 reqwest::blocking::get(url.clone())
192 .context(anyhow::anyhow!("reading peer list url {url}"))?,
193 )
194 .context(anyhow::anyhow!("reading peer list url {url}"))?;
195 Ok(self)
196 }
197
198 pub fn p2p_max_peers(&mut self, limit: usize) -> &mut Self {
199 self.p2p.limits = self.p2p.limits.with_max_peers(Some(limit));
200 self
201 }
202
203 pub fn p2p_custom_task_spawner(
205 &mut self,
206 spawner: impl TaskSpawner,
207 ) -> anyhow::Result<&mut Self> {
208 let sec_key: P2pSecretKey = self.p2p_sec_key.clone().ok_or_else(|| anyhow::anyhow!("before calling `with_p2p_custom_task_spawner` method, p2p secret key needs to be set with `with_p2p_sec_key`."))?;
209 self.service
210 .p2p_init_with_custom_task_spawner(sec_key, spawner);
211 self.p2p_is_started = true;
212 Ok(self)
213 }
214
215 pub fn block_producer(
217 &mut self,
218 key: AccountSecretKey,
219 provers: Option<BlockProver>,
220 ) -> &mut Self {
221 let config = BlockProducerConfig {
222 pub_key: key.public_key().into(),
223 custom_coinbase_receiver: None,
224 proposed_protocol_version: None,
225 };
226 self.block_producer = Some(config);
227 self.service.block_producer_init(key, provers);
228 self
229 }
230
231 pub fn block_producer_from_file(
233 &mut self,
234 path: impl AsRef<Path>,
235 password: &str,
236 provers: Option<BlockProver>,
237 ) -> anyhow::Result<&mut Self> {
238 let key = AccountSecretKey::from_encrypted_file(&path, password).with_context(|| {
239 format!(
240 "Failed to decrypt secret key file: {}",
241 path.as_ref().display()
242 )
243 })?;
244 Ok(self.block_producer(key, provers))
245 }
246
247 pub fn archive(&mut self, options: ArchiveStorageOptions, work_dir: String) -> &mut Self {
248 self.archive = Some(ArchiveConfig::new(work_dir.clone()));
249 self.service.archive_init(options, work_dir.clone());
250 self
251 }
252
253 pub fn custom_coinbase_receiver(
255 &mut self,
256 addr: NonZeroCurvePoint,
257 ) -> anyhow::Result<&mut Self> {
258 let bp = self.block_producer.as_mut().ok_or_else(|| {
259 anyhow::anyhow!(
260 "can't set custom_coinbase_receiver when block producer is not initialized."
261 )
262 })?;
263 bp.custom_coinbase_receiver = Some(addr);
264 Ok(self)
265 }
266
267 pub fn custom_block_producer_config(
268 &mut self,
269 config: BlockProducerConfig,
270 ) -> anyhow::Result<&mut Self> {
271 *self.block_producer.as_mut().ok_or_else(|| {
272 anyhow::anyhow!("block producer not initialized! Call `block_producer` function first.")
273 })? = config;
274 Ok(self)
275 }
276
277 pub fn snarker(
278 &mut self,
279 sec_key: AccountSecretKey,
280 fee: u64,
281 strategy: SnarkerStrategy,
282 ) -> &mut Self {
283 let config = SnarkerConfig {
284 public_key: sec_key.public_key(),
285 fee: v2::CurrencyFeeStableV1(v2::UnsignedExtendedUInt64Int64ForVersionTagsStableV1(
286 fee.into(),
287 )),
288 strategy,
289 auto_commit: true,
290 };
291 self.snarker = Some(config);
292 self
293 }
294
295 pub fn verifier_srs(&mut self, srs: Arc<VerifierSRS>) -> &mut Self {
297 self.verifier_srs = Some(srs);
298 self
299 }
300
301 pub fn block_verifier_index(&mut self, index: BlockVerifier) -> &mut Self {
302 self.block_verifier_index = Some(index);
303 self
304 }
305
306 pub fn work_verifier_index(&mut self, index: TransactionVerifier) -> &mut Self {
307 self.work_verifier_index = Some(index);
308 self
309 }
310
311 pub fn gather_stats(&mut self) -> &mut Self {
312 self.service.gather_stats();
313 self
314 }
315
316 pub fn record(&mut self, recorder: Recorder) -> &mut Self {
317 self.service.record(recorder);
318 self
319 }
320
321 pub fn http_server(&mut self, port: u16) -> &mut Self {
322 self.http_port = Some(port);
323 self.service.http_server_init(port);
324 self
325 }
326
327 pub fn build(mut self) -> anyhow::Result<Node> {
328 let p2p_sec_key = self.p2p_sec_key.clone().unwrap_or_else(P2pSecretKey::rand);
329 self.p2p_sec_key(p2p_sec_key.clone());
330 if self.p2p.initial_peers.is_empty() && !self.p2p_is_seed {
331 self.p2p.initial_peers = default_peers();
332 }
333
334 self.p2p.initial_peers = self
335 .p2p
336 .initial_peers
337 .into_iter()
338 .filter(|opts| *opts.peer_id() != p2p_sec_key.public_key().peer_id())
339 .filter_map(|opts| match opts {
340 P2pConnectionOutgoingInitOpts::LibP2P(mut opts) => {
341 opts.host = opts.host.resolve()?;
342 Some(P2pConnectionOutgoingInitOpts::LibP2P(opts))
343 }
344 x => Some(x),
345 })
346 .collect();
347
348 let srs = self.verifier_srs.unwrap_or_else(get_srs);
349 let block_verifier_index = self
350 .block_verifier_index
351 .unwrap_or_else(BlockVerifier::make);
352 let work_verifier_index = self
353 .work_verifier_index
354 .unwrap_or_else(TransactionVerifier::make);
355
356 let initial_time = self
357 .custom_initial_time
358 .unwrap_or_else(redux::Timestamp::global_now);
359 self.p2p.meshsub.initial_time = initial_time
360 .checked_sub(redux::Timestamp::ZERO)
361 .unwrap_or_default();
362
363 let protocol_constants = self.genesis_config.protocol_constants()?;
364 let consensus_consts =
365 ConsensusConstants::create(constraint_constants(), &protocol_constants);
366
367 let node_config = node::Config {
369 global: GlobalConfig {
370 build: node::BuildEnv::get().into(),
371 snarker: self.snarker,
372 consensus_constants: consensus_consts.clone(),
373 testing_run: false,
374 client_port: self.http_port,
375 },
376 p2p: self.p2p,
377 ledger: LedgerConfig {},
378 snark: SnarkConfig {
379 block_verifier_index,
380 block_verifier_srs: srs.clone(),
381 work_verifier_index,
382 work_verifier_srs: srs,
383 },
384 transition_frontier: TransitionFrontierConfig::new(self.genesis_config),
385 block_producer: self.block_producer,
386 archive: self.archive,
387 tx_pool: ledger::transaction_pool::Config {
388 trust_system: (),
389 pool_max_size: self.daemon_conf.tx_pool_max_size(),
390 slot_tx_end: self.daemon_conf.slot_tx_end(),
391 },
392 };
393
394 let mut service = self.service;
396 service.ledger_init();
397
398 if !self.p2p_is_started {
399 service.p2p_init(p2p_sec_key);
400 }
401
402 let service = service.build()?;
403 let state = node::State::new(node_config, &consensus_consts, initial_time);
404
405 Ok(Node::new(self.rng_seed, state, service, None))
406 }
407}
408
409fn default_peers() -> Vec<P2pConnectionOutgoingInitOpts> {
410 mina_core::NetworkConfig::global()
411 .default_peers
412 .iter()
413 .map(|s| s.parse().unwrap())
414 .collect()
415}
416
417fn peers_from_reader(
418 peers: &mut Vec<P2pConnectionOutgoingInitOpts>,
419 read: impl Read,
420) -> anyhow::Result<()> {
421 let reader = BufReader::new(read);
422 for line in reader.lines() {
423 let line = line.context("reading line")?;
424 let trimmed = line.trim();
425 if trimmed.is_empty() {
426 continue;
427 }
428 match trimmed.parse::<P2pConnectionOutgoingInitOpts>() {
429 Ok(opts) => {
430 if let Some(opts) = opts.with_host_resolved() {
431 peers.push(opts);
432 } else {
433 mina_core::warn!(
434 "Peer address name resolution failed, skipping: {:?}",
435 trimmed
436 );
437 }
438 }
439 Err(e) => mina_core::warn!("Peer address parse error: {:?}", e),
440 }
441 }
442 Ok(())
443}