/ node / src / bootstrap_client / 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 codec;
 20  mod handshake;
 21  mod network;
 22  
 23  use crate::tcp::{self, Tcp};
 24  use alphaos_account::Account;
 25  use alphaos_node_network::{ConnectionMode, Peer, Resolver};
 26  use alphaos_node_tcp::{protocols::*, P2P};
 27  use alphaos_utilities::SignalHandler;
 28  use alphavm::{
 29      ledger::committee::Committee,
 30      prelude::{Address, Field, Header, Network, PrivateKey, ViewKey},
 31      synthesizer::Restrictions,
 32  };
 33  
 34  #[cfg(feature = "locktick")]
 35  use locktick::{
 36      parking_lot::{Mutex, RwLock},
 37      tokio::Mutex as TMutex,
 38  };
 39  #[cfg(not(feature = "locktick"))]
 40  use parking_lot::{Mutex, RwLock};
 41  use std::{
 42      collections::{HashMap, HashSet},
 43      net::SocketAddr,
 44      ops::Deref,
 45      str::FromStr,
 46      sync::Arc,
 47      time::{Duration, Instant},
 48  };
 49  use tokio::sync::oneshot;
 50  #[cfg(not(feature = "locktick"))]
 51  use tokio::sync::Mutex as TMutex;
 52  
 53  #[derive(Clone)]
 54  pub struct BootstrapClient<N: Network>(Arc<InnerBootstrapClient<N>>);
 55  
 56  impl<N: Network> Deref for BootstrapClient<N> {
 57      type Target = Arc<InnerBootstrapClient<N>>;
 58  
 59      fn deref(&self) -> &Self::Target {
 60          &self.0
 61      }
 62  }
 63  
 64  // A tuple holding the validator's Alpha address, and its connection mode.
 65  type KnownValidatorInfo<N> = (Address<N>, ConnectionMode);
 66  
 67  pub struct InnerBootstrapClient<N: Network> {
 68      tcp: Tcp,
 69      peer_pool: RwLock<HashMap<SocketAddr, Peer<N>>>,
 70      known_validators: RwLock<HashMap<SocketAddr, KnownValidatorInfo<N>>>,
 71      resolver: RwLock<Resolver<N>>,
 72      account: Account<N>,
 73      genesis_header: Header<N>,
 74      restrictions_id: Field<N>,
 75      http_client: reqwest::Client,
 76      latest_committee: TMutex<(HashSet<Address<N>>, Instant)>,
 77      dev: Option<u16>,
 78      shutdown_tx: Mutex<Option<oneshot::Sender<()>>>,
 79  }
 80  
 81  impl<N: Network> BootstrapClient<N> {
 82      // The interval for validator committee refreshes.
 83      const COMMITTEE_REFRESH_TIME: Duration = Duration::from_secs(20);
 84      // The maximum amount of time per connection.
 85      const CONNECTION_LIFETIME: Duration = Duration::from_secs(15);
 86      // The maximum number of connected peers.
 87      const MAX_PEERS: u16 = 1_000;
 88  
 89      pub async fn new(
 90          listener_addr: SocketAddr,
 91          account: Account<N>,
 92          genesis_header: Header<N>,
 93          dev: Option<u16>,
 94      ) -> anyhow::Result<Self> {
 95          // Initialize the TCP stack.
 96          let tcp = Tcp::new(tcp::Config::new(listener_addr, Self::MAX_PEERS));
 97          // Initialize the peer pool.
 98          let peer_pool = Default::default();
 99          // Initialize a collection of validators.
100          let known_validators = Default::default();
101          // Load the restrictions ID.
102          let restrictions_id = Restrictions::load()?.restrictions_id();
103          // Create a resolver.
104          let resolver = Default::default();
105          // Create an HTTP client to obtain the current committee.
106          let http_client = reqwest::Client::new();
107          // Prepare a placeholder committee, ensuring that it's insta-outdated.
108          let latest_committee = TMutex::new((Default::default(), Instant::now() - Self::COMMITTEE_REFRESH_TIME));
109  
110          // Prepare the shutdown channel.
111          let (shutdown_tx, shutdown_rx) = oneshot::channel();
112          let shutdown_tx = Mutex::new(Some(shutdown_tx));
113  
114          // Construct and return the bootstrap client.
115          let inner = InnerBootstrapClient {
116              tcp,
117              peer_pool,
118              known_validators,
119              resolver,
120              account,
121              genesis_header,
122              restrictions_id,
123              http_client,
124              latest_committee,
125              dev,
126              shutdown_tx,
127          };
128          let node = BootstrapClient(Arc::new(inner));
129  
130          // Enable the TCP protocols.
131          node.enable_handshake().await;
132          node.enable_reading().await;
133          node.enable_writing().await;
134          node.enable_disconnect().await;
135          node.enable_on_connect().await;
136          // Enable the TCP listener. Note: This must be called after the above protocols.
137          node.tcp().enable_listener().await.expect("Failed to enable the TCP listener");
138  
139          // Await the shutdown signal.
140          let _ = shutdown_rx.await;
141  
142          Ok(node)
143      }
144  
145      /// Returns the account address of the node.
146      pub fn address(&self) -> Address<N> {
147          self.account.address()
148      }
149  
150      /// Returns the account private key of the node.
151      pub fn private_key(&self) -> &PrivateKey<N> {
152          self.account.private_key()
153      }
154  
155      /// Returns the account view key of the node.
156      pub fn view_key(&self) -> &ViewKey<N> {
157          self.account.view_key()
158      }
159  
160      /// Returns the listener IP address from the connected peer address.
161      pub fn resolve_to_listener(&self, connected_addr: SocketAddr) -> Option<SocketAddr> {
162          self.resolver.read().get_listener(connected_addr)
163      }
164  
165      /// Returns `true` if the node is in development mode.
166      pub fn is_dev(&self) -> bool {
167          self.dev.is_some()
168      }
169  
170      /// Returns the current validator committee or updates it from the explorer, if
171      /// we are capable of obtaining it from the network.
172      pub async fn get_or_update_committee(&self) -> anyhow::Result<Option<HashSet<Address<N>>>> {
173          // Development testing may include a list of committee Alpha addresses loaded from the environment.
174          if cfg!(feature = "test") || self.is_dev() {
175              match std::env::var("TEST_COMMITTEE_ADDRS") {
176                  Ok(alpha_addrs) => {
177                      let dev_committee =
178                          alpha_addrs.split(',').map(|addr| Address::<N>::from_str(addr).unwrap()).collect();
179                      return Ok(Some(dev_committee));
180                  }
181                  Err(err) => {
182                      warn!("Failed to load committee peers from environment: {err}");
183                      return Ok(None);
184                  }
185              }
186          }
187  
188          let now = Instant::now();
189          let (committee, timestamp) = &mut *self.latest_committee.lock().await;
190          if now - *timestamp >= Self::COMMITTEE_REFRESH_TIME {
191              debug!("Updating the validator committee");
192              *timestamp = now;
193              let committe_query_addr = format!("https://api.explorer.provable.com/v2/{}/committee/latest", N::NAME);
194              let response = self.http_client.get(committe_query_addr).send().await?;
195              let json = response.text().await?;
196              let full_committee = Committee::from_str(&json)?;
197              *committee = full_committee.members().keys().copied().collect();
198              debug!("The validator committee has {} members now", committee.len());
199  
200              Ok(Some(committee.clone()))
201          } else {
202              Ok(Some(committee.clone()))
203          }
204      }
205  
206      // Return the known addresses of current committee members, or all known
207      // validators if the committee info is unavailable.
208      pub async fn get_validator_addrs(&self) -> HashMap<SocketAddr, KnownValidatorInfo<N>> {
209          // First, collect info on all the validators we had connected to before.
210          let mut known_validators = self.known_validators.read().clone();
211          // If the committee info is available, prune non-committee members.
212          match self.get_or_update_committee().await {
213              Ok(Some(committee)) => {
214                  known_validators.retain(|_, (alpha_addr, _)| committee.contains(alpha_addr));
215                  known_validators
216              }
217              Ok(None) => known_validators,
218              Err(error) => {
219                  error!("Couldn't update the validator committee: {error}");
220                  known_validators
221              }
222          }
223      }
224  
225      /// Shuts down the bootstrap client.
226      pub async fn shut_down(&self) {
227          info!("Shutting down the bootstrap client...");
228  
229          // Shut down the low-level network features.
230          self.tcp.shut_down().await;
231  
232          // Shut down the node.
233          if let Some(shutdown_tx) = self.shutdown_tx.lock().take() {
234              let _ = shutdown_tx.send(());
235          }
236      }
237  
238      /// Blocks until a shutdown signal was received or manual shutdown was triggered.
239      pub async fn wait_for_signals(&self, handler: &SignalHandler) {
240          handler.wait_for_signals().await;
241  
242          // If the node is already initialized, then shut it down.
243          self.shut_down().await;
244      }
245  }