Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add spawnable nodes and networks #2642

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ant-evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub use evmlib::contract::payment_vault;
pub use evmlib::cryptography;
#[cfg(feature = "external-signer")]
pub use evmlib::external_signer;
pub use evmlib::testnet::Testnet as EvmTestnet;
pub use evmlib::utils;
pub use evmlib::utils::get_evm_network;
pub use evmlib::utils::{DATA_PAYMENTS_ADDRESS, PAYMENT_TOKEN_ADDRESS, RPC_URL};
Expand Down
81 changes: 3 additions & 78 deletions ant-node/src/bin/antnode/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use ant_bootstrap::{BootstrapCacheConfig, BootstrapCacheStore, PeersArgs};
use ant_evm::{get_evm_network, EvmNetwork, RewardsAddress};
use ant_logging::metrics::init_metrics;
use ant_logging::{Level, LogFormat, LogOutputDest, ReloadHandle};
use ant_node::utils::get_root_dir_and_keypair;
use ant_node::{Marker, NodeBuilder, NodeEvent, NodeEventsReceiver};
use ant_protocol::{
node::get_antnode_root_dir,
Expand All @@ -26,12 +27,11 @@ use ant_protocol::{
use clap::{command, Parser};
use color_eyre::{eyre::eyre, Result};
use const_hex::traits::FromHex;
use libp2p::{identity::Keypair, PeerId};
use libp2p::PeerId;
use std::{
env,
io::Write,
net::{IpAddr, Ipv4Addr, SocketAddr},
path::{Path, PathBuf},
path::PathBuf,
process::Command,
time::Duration,
};
Expand Down Expand Up @@ -623,81 +623,6 @@ fn init_logging(opt: &Opt, peer_id: PeerId) -> Result<(String, ReloadHandle, Opt
Ok((output_dest.to_string(), reload_handle, log_appender_guard))
}

fn create_secret_key_file(path: impl AsRef<Path>) -> Result<std::fs::File, std::io::Error> {
let mut opt = std::fs::OpenOptions::new();
opt.write(true).create_new(true);

// On Unix systems, make sure only the current user can read/write.
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
opt.mode(0o600);
}

opt.open(path)
}

fn keypair_from_path(path: impl AsRef<Path>) -> Result<Keypair> {
let keypair = match std::fs::read(&path) {
// If the file is opened successfully, read the key from it
Ok(key) => {
let keypair = Keypair::ed25519_from_bytes(key)
.map_err(|err| eyre!("could not read ed25519 key from file: {err}"))?;

info!("loaded secret key from file: {:?}", path.as_ref());

keypair
}
// In case the file is not found, generate a new keypair and write it to the file
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
let secret_key = libp2p::identity::ed25519::SecretKey::generate();
let mut file = create_secret_key_file(&path)
.map_err(|err| eyre!("could not create secret key file: {err}"))?;
file.write_all(secret_key.as_ref())?;

info!("generated new key and stored to file: {:?}", path.as_ref());

libp2p::identity::ed25519::Keypair::from(secret_key).into()
}
// Else the file can't be opened, for whatever reason (e.g. permissions).
Err(err) => {
return Err(eyre!("failed to read secret key file: {err}"));
}
};

Ok(keypair)
}

/// The keypair is located inside the root directory. At the same time, when no dir is specified,
/// the dir name is derived from the keypair used in the application: the peer ID is used as the directory name.
fn get_root_dir_and_keypair(root_dir: &Option<PathBuf>) -> Result<(PathBuf, Keypair)> {
match root_dir {
Some(dir) => {
std::fs::create_dir_all(dir)?;

let secret_key_path = dir.join("secret-key");
Ok((dir.clone(), keypair_from_path(secret_key_path)?))
}
None => {
let secret_key = libp2p::identity::ed25519::SecretKey::generate();
let keypair: Keypair =
libp2p::identity::ed25519::Keypair::from(secret_key.clone()).into();
let peer_id = keypair.public().to_peer_id();

let dir = get_antnode_root_dir(peer_id)?;
std::fs::create_dir_all(&dir)?;

let secret_key_path = dir.join("secret-key");

let mut file = create_secret_key_file(secret_key_path)
.map_err(|err| eyre!("could not create secret key file: {err}"))?;
file.write_all(secret_key.as_ref())?;

Ok((dir, keypair))
}
}
}

/// Starts a new process running the binary with the same args as
/// the current process
/// Optionally provide the node's root dir and listen port to retain it's PeerId
Expand Down
15 changes: 12 additions & 3 deletions ant-node/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ mod put_validation;
mod python;
mod quote;
mod replication;
#[allow(missing_docs)]
pub mod spawn;
#[allow(missing_docs)]
pub mod utils;

pub use self::{
event::{NodeEvent, NodeEventsChannel, NodeEventsReceiver},
Expand All @@ -41,16 +45,15 @@ pub use self::{

use crate::error::{Error, Result};

use ant_evm::RewardsAddress;
use ant_networking::{Network, SwarmLocalState};
use ant_protocol::{get_port_from_multiaddr, NetworkAddress};
use libp2p::PeerId;
use libp2p::{Multiaddr, PeerId};
use std::{
collections::{BTreeMap, HashSet},
path::PathBuf,
};

use ant_evm::RewardsAddress;

/// Once a node is started and running, the user obtains
/// a `NodeRunning` object which can be used to interact with it.
#[derive(Clone)]
Expand Down Expand Up @@ -85,6 +88,12 @@ impl RunningNode {
Ok(state)
}

/// Return the node's listening addresses.
pub async fn get_listen_addrs(&self) -> Result<Vec<Multiaddr>> {
let listeners = self.network.get_swarm_local_state().await?.listeners;
Ok(listeners)
}

/// Return the node's listening port
pub async fn get_node_listening_port(&self) -> Result<u16> {
let listen_addrs = self.network.get_swarm_local_state().await?.listeners;
Expand Down
3 changes: 1 addition & 2 deletions ant-node/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::metrics::NodeMetricsRecorder;
use crate::RunningNode;
use ant_bootstrap::BootstrapCacheStore;
use ant_evm::RewardsAddress;
use ant_evm::{EvmNetwork, U256};
#[cfg(feature = "open-metrics")]
use ant_networking::MetricsRegistries;
use ant_networking::{
Expand Down Expand Up @@ -49,8 +50,6 @@ use tokio::{
task::{spawn, JoinSet},
};

use ant_evm::{EvmNetwork, U256};

/// Interval to trigger replication of all records to all peers.
/// This is the max time it should take. Minimum interval at any node will be half this
pub const PERIODIC_REPLICATION_INTERVAL_MAX_S: u64 = 180;
Expand Down
2 changes: 2 additions & 0 deletions ant-node/src/spawn/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod network;
pub mod node;
173 changes: 173 additions & 0 deletions ant-node/src/spawn/network.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
use crate::spawn::node::NodeSpawner;
use crate::RunningNode;
use ant_evm::{EvmNetwork, RewardsAddress};
use libp2p::Multiaddr;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::path::PathBuf;

pub struct NetworkSpawner {
evm_network: EvmNetwork,
rewards_address: RewardsAddress,
local: bool,
upnp: bool,
root_dir: Option<PathBuf>,
size: usize,
}

impl NetworkSpawner {
pub fn new() -> Self {
Self {
evm_network: Default::default(),
rewards_address: Default::default(),
local: false,
upnp: false,
root_dir: None,
size: 5,
}
}

/// Sets the EVM network.
pub fn with_evm_network(mut self, evm_network: EvmNetwork) -> Self {
self.evm_network = evm_network;
self
}

/// Sets the rewards address.
pub fn with_rewards_address(mut self, rewards_address: RewardsAddress) -> Self {
self.rewards_address = rewards_address;
self
}

/// Sets the local mode value.
pub fn with_local(mut self, value: bool) -> Self {
self.local = value;
self
}

/// Sets the UPnP value (automatic port forwarding).
pub fn with_upnp(mut self, value: bool) -> Self {
self.upnp = value;
self
}

/// Sets the root directory for the nodes.
pub fn with_root_dir(mut self, root_dir: Option<PathBuf>) -> Self {
self.root_dir = root_dir;
self
}

/// Sets the amount of nodes spawned in the network.
pub fn with_size(mut self, size: usize) -> Self {
self.size = size;
self
}

pub async fn spawn(self) -> eyre::Result<SpawnedNetwork> {
spawn_network(
self.evm_network,
self.rewards_address,
self.local,
self.upnp,
self.root_dir,
self.size,
)
.await
}
}

impl Default for NetworkSpawner {
fn default() -> Self {
Self::new()
}
}

pub struct SpawnedNetwork {
running_nodes: Vec<RunningNode>,
}

impl SpawnedNetwork {
pub fn running_nodes(&self) -> &Vec<RunningNode> {
&self.running_nodes
}
}

async fn spawn_network(
evm_network: EvmNetwork,
rewards_address: RewardsAddress,
local: bool,
upnp: bool,
root_dir: Option<PathBuf>,
size: usize,
) -> eyre::Result<SpawnedNetwork> {
let mut running_nodes: Vec<RunningNode> = vec![];

for i in 0..size {
let ip = match local {
true => IpAddr::V4(Ipv4Addr::LOCALHOST),
false => IpAddr::V4(Ipv4Addr::UNSPECIFIED),
};

let socket_addr = SocketAddr::new(ip, 0);

let mut initial_peers: Vec<Multiaddr> = vec![];

for peer in running_nodes.iter() {
if let Ok(listen_addrs) = peer.get_listen_addrs().await {
initial_peers.extend(listen_addrs);
}
}

let node = NodeSpawner::new()
.with_socket_addr(socket_addr)
.with_evm_network(evm_network.clone())
.with_rewards_address(rewards_address)
.with_initial_peers(initial_peers)
.with_local(local)
.with_upnp(upnp)
.with_root_dir(root_dir.clone())
.spawn()
.await?;

let listen_addrs = node.get_listen_addrs().await;

info!(
"Spawned node #{} with listen addresses: {:?}",
i + 1,
listen_addrs
);

running_nodes.push(node);
}

Ok(SpawnedNetwork { running_nodes })
}

#[cfg(test)]
mod tests {
use super::*;
use ant_evm::EvmTestnet;

#[tokio::test]
async fn test_spawn_network() {
// start local Ethereum node
let evm_testnet = EvmTestnet::new().await;
let evm_network = evm_testnet.to_network();
let network_size = 20;

let spawned_network = NetworkSpawner::new()
.with_evm_network(evm_network)
.with_size(network_size)
.spawn()
.await
.unwrap();

assert_eq!(spawned_network.running_nodes().len(), network_size);

// Validate each node's listen addresses are not empty
for node in spawned_network.running_nodes() {
let listen_addrs = node.get_listen_addrs().await.unwrap();

assert!(!listen_addrs.is_empty());
}
}
}
Loading
Loading