/ node / src / prover / mod.rs
mod.rs
  1  // Copyright (c) 2025-2026 ACDC Network
  2  // This file is part of the alphaos library.
  3  //
  4  // Alpha Chain | Delta Chain Protocol
  5  // International Monetary Graphite.
  6  //
  7  // Derived from Aleo (https://aleo.org) and ProvableHQ (https://provable.com).
  8  // They built world-class ZK infrastructure. We installed the EASY button.
  9  // Their cryptography: elegant. Our modifications: bureaucracy-compatible.
 10  // Original brilliance: theirs. Robert's Rules: ours. Bugs: definitely ours.
 11  //
 12  // Original Aleo/ProvableHQ code subject to Apache 2.0 https://www.apache.org/licenses/LICENSE-2.0
 13  // All modifications and new work: CC0 1.0 Universal Public Domain Dedication.
 14  // No rights reserved. No permission required. No warranty. No refunds.
 15  //
 16  // https://creativecommons.org/publicdomain/zero/1.0/
 17  // SPDX-License-Identifier: CC0-1.0
 18  
 19  mod router;
 20  
 21  use crate::{
 22      bft::ledger_service::ProverLedgerService,
 23      sync::{BlockSync, Ping},
 24      traits::NodeInterface,
 25  };
 26  
 27  use alphaos_account::Account;
 28  use alphaos_node_network::{NodeType, PeerPoolHandling};
 29  
 30  use alphaos_node_router::{
 31      messages::{Message, UnconfirmedSolution},
 32      Heartbeat,
 33      Inbound,
 34      Outbound,
 35      Router,
 36      Routing,
 37  };
 38  use alphaos_node_tcp::{
 39      protocols::{Disconnect, Handshake, OnConnect, Reading},
 40      P2P,
 41  };
 42  use alphaos_utilities::{SignalHandler, Stoppable};
 43  
 44  use alphavm::{
 45      ledger::narwhal::Data,
 46      prelude::{
 47          block::{Block, Header},
 48          puzzle::{Puzzle, Solution},
 49          store::ConsensusStorage,
 50          Network,
 51      },
 52      synthesizer::VM,
 53  };
 54  
 55  use alphaos_node_bft::helpers::fmt_id;
 56  use alphastd::StorageMode;
 57  use anyhow::Result;
 58  use colored::Colorize;
 59  use core::{marker::PhantomData, time::Duration};
 60  #[cfg(feature = "locktick")]
 61  use locktick::parking_lot::{Mutex, RwLock};
 62  #[cfg(not(feature = "locktick"))]
 63  use parking_lot::{Mutex, RwLock};
 64  use rand::{rngs::OsRng, CryptoRng, Rng};
 65  use std::{
 66      net::SocketAddr,
 67      sync::{
 68          atomic::{AtomicU8, Ordering},
 69          Arc,
 70      },
 71  };
 72  use tokio::task::JoinHandle;
 73  
 74  /// A prover is a light node, capable of producing proofs for consensus.
 75  #[derive(Clone)]
 76  pub struct Prover<N: Network, C: ConsensusStorage<N>> {
 77      /// The router of the node.
 78      router: Router<N>,
 79      /// The sync module.
 80      sync: Arc<BlockSync<N>>,
 81      /// The genesis block.
 82      genesis: Block<N>,
 83      /// The puzzle.
 84      puzzle: Puzzle<N>,
 85      /// The latest epoch hash.
 86      latest_epoch_hash: Arc<RwLock<Option<N::BlockHash>>>,
 87      /// The latest block header.
 88      latest_block_header: Arc<RwLock<Option<Header<N>>>>,
 89      /// The number of puzzle instances.
 90      puzzle_instances: Arc<AtomicU8>,
 91      /// The maximum number of puzzle instances.
 92      max_puzzle_instances: u8,
 93      /// The spawned handles.
 94      handles: Arc<Mutex<Vec<JoinHandle<()>>>>,
 95      /// Keeps track of sending pings.
 96      ping: Arc<Ping<N>>,
 97      /// The signal handling logic.
 98      signal_handler: Arc<SignalHandler>,
 99      /// PhantomData.
100      _phantom: PhantomData<C>,
101  }
102  
103  impl<N: Network, C: ConsensusStorage<N>> Prover<N, C> {
104      /// Initializes a new prover node.
105      pub async fn new(
106          node_ip: SocketAddr,
107          account: Account<N>,
108          trusted_peers: &[SocketAddr],
109          genesis: Block<N>,
110          storage_mode: StorageMode,
111          trusted_peers_only: bool,
112          dev: Option<u16>,
113          signal_handler: Arc<SignalHandler>,
114      ) -> Result<Self> {
115          // Initialize the ledger service.
116          let ledger_service = Arc::new(ProverLedgerService::new());
117  
118          // Initialize the node router.
119          let router = Router::new(
120              node_ip,
121              NodeType::Prover,
122              account,
123              ledger_service.clone(),
124              trusted_peers,
125              Self::MAXIMUM_NUMBER_OF_PEERS as u16,
126              trusted_peers_only,
127              storage_mode,
128              dev.is_some(),
129          )
130          .await?;
131  
132          // Initialize the sync module.
133          let sync = BlockSync::new(ledger_service.clone());
134  
135          // Set up the ping logic.
136          let ping = Arc::new(Ping::new_nosync(router.clone()));
137  
138          // Compute the maximum number of puzzle instances.
139          let max_puzzle_instances = num_cpus::get().saturating_sub(2).clamp(1, 6);
140          // Initialize the node.
141          let node = Self {
142              router,
143              sync: Arc::new(sync),
144              genesis,
145              puzzle: VM::<N, C>::new_puzzle()?,
146              latest_epoch_hash: Default::default(),
147              latest_block_header: Default::default(),
148              puzzle_instances: Default::default(),
149              max_puzzle_instances: u8::try_from(max_puzzle_instances)?,
150              handles: Default::default(),
151              ping,
152              signal_handler,
153              _phantom: Default::default(),
154          };
155          // Initialize the routing.
156          node.initialize_routing().await;
157          // Initialize the puzzle.
158          node.initialize_puzzle().await;
159          // Initialize the notification message loop.
160          node.handles.lock().push(crate::start_notification_message_loop());
161  
162          // Return the node.
163          Ok(node)
164      }
165  
166      pub fn router(&self) -> &Router<N> {
167          &self.router
168      }
169  }
170  
171  #[async_trait]
172  impl<N: Network, C: ConsensusStorage<N>> NodeInterface<N> for Prover<N, C> {
173      /// Shuts down the node.
174      async fn shut_down(&self) {
175          info!("Shutting down...");
176  
177          // Shut down the puzzle.
178          debug!("Shutting down the puzzle...");
179  
180          // Abort the tasks.
181          debug!("Shutting down the prover...");
182          self.handles.lock().iter().for_each(|handle| handle.abort());
183  
184          // Shut down the router.
185          self.router.shut_down().await;
186  
187          info!("Node has shut down.");
188      }
189  }
190  
191  impl<N: Network, C: ConsensusStorage<N>> Prover<N, C> {
192      /// Initialize a new instance of the puzzle.
193      async fn initialize_puzzle(&self) {
194          for _ in 0..self.max_puzzle_instances {
195              let prover = self.clone();
196              self.handles.lock().push(tokio::spawn(async move {
197                  prover.puzzle_loop().await;
198              }));
199          }
200      }
201  
202      /// Executes an instance of the puzzle.
203      async fn puzzle_loop(&self) {
204          loop {
205              // If the node is not connected to any peers, then skip this iteration.
206              if self.router.number_of_connected_peers() == 0 {
207                  debug!("Skipping an iteration of the puzzle (no connected peers)");
208                  tokio::time::sleep(Duration::from_secs(N::ANCHOR_TIME as u64)).await;
209                  continue;
210              }
211  
212              // If the number of instances of the puzzle exceeds the maximum, then skip this iteration.
213              if self.num_puzzle_instances() > self.max_puzzle_instances {
214                  // Sleep for a brief period of time.
215                  tokio::time::sleep(Duration::from_millis(500)).await;
216                  continue;
217              }
218  
219              // Read the latest epoch hash.
220              let latest_epoch_hash = *self.latest_epoch_hash.read();
221              // Read the latest state.
222              let latest_state = self
223                  .latest_block_header
224                  .read()
225                  .as_ref()
226                  .map(|header| (header.coinbase_target(), header.proof_target()));
227  
228              // If the latest epoch hash and latest state exists, then proceed to generate a solution.
229              if let (Some(epoch_hash), Some((coinbase_target, proof_target))) = (latest_epoch_hash, latest_state) {
230                  // Execute the puzzle.
231                  let prover = self.clone();
232                  let result = tokio::task::spawn_blocking(move || {
233                      prover.puzzle_iteration(epoch_hash, coinbase_target, proof_target, &mut OsRng)
234                  })
235                  .await;
236  
237                  // If the prover found a solution, then broadcast it.
238                  if let Ok(Some((solution_target, solution))) = result {
239                      info!("Found a Solution '{}' (Proof Target {solution_target})", solution.id());
240                      // Broadcast the solution.
241                      self.broadcast_solution(solution);
242                  }
243              } else {
244                  // Otherwise, sleep for a brief period of time, to await for puzzle state.
245                  tokio::time::sleep(Duration::from_secs(1)).await;
246              }
247  
248              // If the Ctrl-C handler registered the signal, stop the prover.
249              if self.signal_handler.is_stopped() {
250                  debug!("Shutting down the puzzle...");
251                  break;
252              }
253          }
254      }
255  
256      /// Performs one iteration of the puzzle.
257      fn puzzle_iteration<R: Rng + CryptoRng>(
258          &self,
259          epoch_hash: N::BlockHash,
260          coinbase_target: u64,
261          proof_target: u64,
262          rng: &mut R,
263      ) -> Option<(u64, Solution<N>)> {
264          // Increment the puzzle instances.
265          self.increment_puzzle_instances();
266  
267          debug!(
268              "Proving 'Puzzle' for Epoch '{}' {}",
269              fmt_id(epoch_hash),
270              format!("(Coinbase Target {coinbase_target}, Proof Target {proof_target})").dimmed()
271          );
272  
273          // Compute the solution.
274          let result =
275              self.puzzle.prove(epoch_hash, self.address(), rng.r#gen(), Some(proof_target)).ok().and_then(|solution| {
276                  self.puzzle.get_proof_target(&solution).ok().map(|solution_target| (solution_target, solution))
277              });
278  
279          // Decrement the puzzle instances.
280          self.decrement_puzzle_instances();
281          // Return the result.
282          result
283      }
284  
285      /// Broadcasts the solution to the network.
286      fn broadcast_solution(&self, solution: Solution<N>) {
287          // Prepare the unconfirmed solution message.
288          let message = Message::UnconfirmedSolution(UnconfirmedSolution {
289              solution_id: solution.id(),
290              solution: Data::Object(solution),
291          });
292          // Propagate the "UnconfirmedSolution".
293          self.propagate(message, &[]);
294      }
295  
296      /// Returns the current number of puzzle instances.
297      fn num_puzzle_instances(&self) -> u8 {
298          self.puzzle_instances.load(Ordering::Relaxed)
299      }
300  
301      /// Increments the number of puzzle instances.
302      fn increment_puzzle_instances(&self) {
303          self.puzzle_instances.fetch_add(1, Ordering::Relaxed);
304          #[cfg(debug_assertions)]
305          trace!("Number of Instances - {}", self.num_puzzle_instances());
306      }
307  
308      /// Decrements the number of puzzle instances.
309      fn decrement_puzzle_instances(&self) {
310          self.puzzle_instances.fetch_sub(1, Ordering::Relaxed);
311          #[cfg(debug_assertions)]
312          trace!("Number of Instances - {}", self.num_puzzle_instances());
313      }
314  }