414 lines
13 KiB
Rust
414 lines
13 KiB
Rust
use std::fmt::Display;
|
|
use std::future::Future;
|
|
use std::sync::Arc;
|
|
|
|
use futures_util::future::BoxFuture;
|
|
use futures_util::FutureExt;
|
|
use log::{debug, error, info};
|
|
use tokio::sync::Mutex;
|
|
|
|
use crate::core::shutdown::Shutdown;
|
|
#[cfg(feature = "rocksdb_storage")]
|
|
use crate::storage::rocksdb::RocksDbStorage;
|
|
#[cfg(feature = "sqlite_storage")]
|
|
use crate::storage::sqlite::SqliteStorage;
|
|
use crate::{
|
|
api::{application::Application, http, ApiListener, CommandExecutor},
|
|
block::{builder::BlockManagerBuilder, manager::BlockManager},
|
|
broadcast::bracha::broadcast::Broadcaster,
|
|
broadcast::group::BroadcastGroup,
|
|
config::Configuration,
|
|
core::{
|
|
api_cmd::ApiCmdProcessor,
|
|
shutdown::{Handle, ShutdownManager},
|
|
},
|
|
crypto::Keypair,
|
|
membership,
|
|
membership::PeerInfo,
|
|
network::libp2p::{
|
|
ephemera_sender::EphemeraToNetworkSender, network_sender::NetCommunicationReceiver,
|
|
swarm_network::SwarmNetwork,
|
|
},
|
|
peer::{PeerId, ToPeerId},
|
|
storage::EphemeraDatabase,
|
|
utilities::crypto::key_manager::KeyManager,
|
|
websocket::ws_manager::{WsManager, WsMessageBroadcaster},
|
|
Ephemera,
|
|
};
|
|
|
|
#[derive(Clone)]
|
|
pub(crate) struct NodeInfo {
|
|
pub(crate) ip: String,
|
|
pub(crate) protocol_port: u16,
|
|
pub(crate) http_port: u16,
|
|
pub(crate) ws_port: u16,
|
|
pub(crate) peer_id: PeerId,
|
|
pub(crate) keypair: Arc<Keypair>,
|
|
pub(crate) initial_config: Configuration,
|
|
}
|
|
|
|
impl NodeInfo {
|
|
pub(crate) fn new(config: Configuration) -> anyhow::Result<Self> {
|
|
let keypair = KeyManager::read_keypair_from_str(&config.node.private_key)?;
|
|
let info = Self {
|
|
ip: config.node.ip.clone(),
|
|
protocol_port: config.libp2p.port,
|
|
http_port: config.http.port,
|
|
ws_port: config.websocket.port,
|
|
peer_id: keypair.peer_id(),
|
|
keypair,
|
|
initial_config: config,
|
|
};
|
|
Ok(info)
|
|
}
|
|
|
|
pub(crate) fn protocol_address(&self) -> String {
|
|
format!("/ip4/{}/tcp/{}", self.ip, self.protocol_port)
|
|
}
|
|
|
|
pub(crate) fn api_address_http(&self) -> String {
|
|
format!("http://{}:{}", self.ip, self.http_port)
|
|
}
|
|
|
|
pub(crate) fn ws_address_ws(&self) -> String {
|
|
format!("ws://{}:{}", self.ip, self.ws_port)
|
|
}
|
|
|
|
pub(crate) fn ws_address_ip_port(&self) -> String {
|
|
format!("{}:{}", self.ip, self.ws_port)
|
|
}
|
|
}
|
|
|
|
impl Display for NodeInfo {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(
|
|
f,
|
|
"NodeInfo {{ ip: {}, protocol_port: {}, http_port: {}, ws_port: {}, peer_id: {}, keypair: {} }}",
|
|
self.ip,
|
|
self.protocol_port,
|
|
self.http_port,
|
|
self.ws_port,
|
|
self.peer_id,
|
|
self.keypair
|
|
)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct EphemeraHandle {
|
|
/// Ephemera API
|
|
pub api: CommandExecutor,
|
|
/// Allows to send shutdown signal to the node
|
|
pub shutdown: Handle,
|
|
}
|
|
|
|
pub struct EphemeraStarterInit {
|
|
config: Configuration,
|
|
node_info: NodeInfo,
|
|
broadcaster: Broadcaster,
|
|
api_listener: ApiListener,
|
|
api: CommandExecutor,
|
|
}
|
|
|
|
impl EphemeraStarterInit {
|
|
/// Initialize Ephemera builder
|
|
///
|
|
/// # Arguments
|
|
/// * `config` - [Configuration]
|
|
///
|
|
/// # Returns
|
|
/// [`EphemeraStarterInit`]
|
|
///
|
|
/// # Errors
|
|
/// * If the node configuration is invalid
|
|
pub fn new(config: Configuration) -> anyhow::Result<Self> {
|
|
let instance_info = NodeInfo::new(config.clone())?;
|
|
let broadcaster = Broadcaster::new(instance_info.peer_id);
|
|
let (api, api_listener) = CommandExecutor::new();
|
|
|
|
let builder = EphemeraStarterInit {
|
|
config,
|
|
node_info: instance_info,
|
|
broadcaster,
|
|
api_listener,
|
|
api,
|
|
};
|
|
Ok(builder)
|
|
}
|
|
|
|
pub fn with_application<A: Application>(
|
|
self,
|
|
application: A,
|
|
) -> EphemeraStarterWithApplication<A> {
|
|
EphemeraStarterWithApplication {
|
|
init: self,
|
|
application,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct ServiceInfo {
|
|
ws_message_broadcast: Option<WsMessageBroadcaster>,
|
|
from_network: Option<NetCommunicationReceiver>,
|
|
to_network: Option<EphemeraToNetworkSender>,
|
|
}
|
|
|
|
pub struct EphemeraStarterWithApplication<A: Application> {
|
|
init: EphemeraStarterInit,
|
|
application: A,
|
|
}
|
|
|
|
impl<A: Application> EphemeraStarterWithApplication<A> {
|
|
/// Initialize Ephemera with the given application.
|
|
/// It also tries to open the database connection.
|
|
///
|
|
/// # Arguments
|
|
/// * `application` - [Application] to be used
|
|
///
|
|
/// # Returns
|
|
/// [`EphemeraStarterWithApplication`]
|
|
///
|
|
/// # Errors
|
|
/// * If the node configuration is invalid or the database connection cannot be opened
|
|
pub fn with_members_provider<
|
|
P: Future<Output = membership::Result<Vec<PeerInfo>>> + Send + Unpin + 'static,
|
|
>(
|
|
mut self,
|
|
provider: P,
|
|
) -> anyhow::Result<EphemeraStarterWithProvider<A>> {
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(feature = "sqlite_storage")] {
|
|
let mut storage = self.connect_sqlite()?;
|
|
debug!("Connected to sqlite database")
|
|
} else if #[cfg(feature = "rocksdb_storage")] {
|
|
let mut storage = self.connect_rocksdb()?;
|
|
debug!("Connected to rocksdb database")
|
|
} else {
|
|
compile_error!("Must enable either sqlite or rocksdb feature");
|
|
}
|
|
}
|
|
|
|
let block_manager = self.init_block_manager(&mut storage)?;
|
|
|
|
let (shutdown_manager, shutdown_handle) = ShutdownManager::init();
|
|
|
|
let mut service_data = ServiceInfo::default();
|
|
let services = self.init_services(&mut service_data, &shutdown_manager, provider)?;
|
|
|
|
Ok(EphemeraStarterWithProvider {
|
|
with_application: self,
|
|
block_manager: Some(block_manager),
|
|
service_data,
|
|
services,
|
|
storage: Some(Box::new(storage)),
|
|
shutdown_manager: Some(shutdown_manager),
|
|
shutdown_handle: Some(shutdown_handle),
|
|
})
|
|
}
|
|
|
|
//allocate database connection
|
|
#[cfg(feature = "rocksdb_storage")]
|
|
fn connect_rocksdb(&self) -> anyhow::Result<RocksDbStorage> {
|
|
info!("Opening database...");
|
|
RocksDbStorage::open(&self.init.config.storage)
|
|
.map_err(|e| anyhow::anyhow!("Failed to open database: {}", e))
|
|
}
|
|
|
|
#[cfg(feature = "sqlite_storage")]
|
|
fn connect_sqlite(&mut self) -> anyhow::Result<SqliteStorage> {
|
|
info!("Opening database...");
|
|
SqliteStorage::open(self.init.config.storage.clone())
|
|
.map_err(|e| anyhow::anyhow!("Failed to open database: {}", e))
|
|
}
|
|
|
|
fn init_block_manager<D: EphemeraDatabase + ?Sized>(
|
|
&mut self,
|
|
db: &mut D,
|
|
) -> anyhow::Result<BlockManager> {
|
|
let block_manager_configuration = self.init.config.block_manager.clone();
|
|
let keypair = self.init.node_info.keypair.clone();
|
|
let builder = BlockManagerBuilder::new(block_manager_configuration, keypair);
|
|
builder.build(db)
|
|
}
|
|
|
|
fn init_services<
|
|
P: Future<Output = membership::Result<Vec<PeerInfo>>> + Send + Unpin + 'static,
|
|
>(
|
|
&mut self,
|
|
service_data: &mut ServiceInfo,
|
|
shutdown_manager: &ShutdownManager,
|
|
provider: P,
|
|
) -> anyhow::Result<Vec<BoxFuture<'static, anyhow::Result<()>>>> {
|
|
let services = vec![
|
|
self.init_libp2p(service_data, shutdown_manager.subscribe(), provider)?,
|
|
self.init_http(shutdown_manager.subscribe())?,
|
|
self.init_websocket(service_data, shutdown_manager.subscribe()),
|
|
];
|
|
Ok(services)
|
|
}
|
|
|
|
fn init_websocket(
|
|
&mut self,
|
|
service_data: &mut ServiceInfo,
|
|
mut shutdown: Shutdown,
|
|
) -> BoxFuture<'static, anyhow::Result<()>> {
|
|
let (mut websocket, ws_message_broadcast) =
|
|
WsManager::new(self.init.node_info.ws_address_ip_port());
|
|
|
|
service_data.ws_message_broadcast = Some(ws_message_broadcast);
|
|
|
|
async move {
|
|
websocket.listen().await?;
|
|
|
|
tokio::select! {
|
|
_ = shutdown.shutdown_signal_rcv.recv() => {
|
|
info!("Shutting down websocket manager");
|
|
}
|
|
ws_stopped = websocket.run() => {
|
|
match ws_stopped {
|
|
Ok(()) => info!("Websocket stopped unexpectedly"),
|
|
Err(e) => error!("Websocket stopped with error: {}", e),
|
|
}
|
|
}
|
|
}
|
|
info!("Websocket task finished");
|
|
Ok(())
|
|
}
|
|
.boxed()
|
|
}
|
|
|
|
fn init_http(
|
|
&mut self,
|
|
mut shutdown: Shutdown,
|
|
) -> anyhow::Result<BoxFuture<'static, anyhow::Result<()>>> {
|
|
let http = http::init(&self.init.node_info, self.init.api.clone())?;
|
|
|
|
let fut = async move {
|
|
let server_handle = http.handle();
|
|
tokio::select! {
|
|
_ = shutdown.shutdown_signal_rcv.recv() => {
|
|
info!("Shutting down http server");
|
|
server_handle.stop(true).await;
|
|
}
|
|
http_stopped = http => {
|
|
match http_stopped {
|
|
Ok(()) => info!("Http server stopped unexpectedly"),
|
|
Err(e) => error!("Http server stopped with error: {}", e),
|
|
}
|
|
}
|
|
}
|
|
info!("Http task finished");
|
|
Ok(())
|
|
}
|
|
.boxed();
|
|
Ok(fut)
|
|
}
|
|
|
|
fn init_libp2p<
|
|
P: Future<Output = membership::Result<Vec<PeerInfo>>> + Send + Unpin + 'static,
|
|
>(
|
|
&mut self,
|
|
service_data: &mut ServiceInfo,
|
|
mut shutdown: Shutdown,
|
|
provider: P,
|
|
) -> anyhow::Result<BoxFuture<'static, anyhow::Result<()>>> {
|
|
info!("Starting network...",);
|
|
|
|
let (mut network, from_network, to_network) =
|
|
SwarmNetwork::new(self.init.node_info.clone(), provider)?;
|
|
|
|
service_data.from_network = Some(from_network);
|
|
service_data.to_network = Some(to_network);
|
|
|
|
let libp2p = async move {
|
|
network.listen()?;
|
|
|
|
tokio::select! {
|
|
_ = shutdown.shutdown_signal_rcv.recv() => {
|
|
info!("Shutting down network");
|
|
}
|
|
nw_stopped = network.start() => {
|
|
match nw_stopped {
|
|
Ok(()) => info!("Network stopped unexpectedly"),
|
|
Err(e) => error!("Network stopped with error: {e}",),
|
|
}
|
|
}
|
|
}
|
|
info!("Network task finished");
|
|
Ok(())
|
|
}
|
|
.boxed();
|
|
Ok(libp2p)
|
|
}
|
|
}
|
|
|
|
pub struct EphemeraStarterWithProvider<A>
|
|
where
|
|
A: Application + 'static,
|
|
{
|
|
with_application: EphemeraStarterWithApplication<A>,
|
|
block_manager: Option<BlockManager>,
|
|
service_data: ServiceInfo,
|
|
storage: Option<Box<dyn EphemeraDatabase>>,
|
|
services: Vec<BoxFuture<'static, anyhow::Result<()>>>,
|
|
shutdown_manager: Option<ShutdownManager>,
|
|
shutdown_handle: Option<Handle>,
|
|
}
|
|
|
|
impl<A> EphemeraStarterWithProvider<A>
|
|
where
|
|
A: Application + 'static,
|
|
{
|
|
pub fn build(self) -> Ephemera<A> {
|
|
self.ephemera()
|
|
}
|
|
|
|
fn ephemera(mut self) -> Ephemera<A> {
|
|
let ephemera_handle = EphemeraHandle {
|
|
api: self.with_application.init.api,
|
|
shutdown: self.shutdown_handle.take().unwrap(),
|
|
};
|
|
|
|
let node_info = self.with_application.init.node_info;
|
|
let application = self.with_application.application;
|
|
let block_manager = self.block_manager.expect("Block manager not initialized");
|
|
let broadcaster = self.with_application.init.broadcaster;
|
|
let from_network = self
|
|
.service_data
|
|
.from_network
|
|
.expect("From network not initialized");
|
|
let to_network = self
|
|
.service_data
|
|
.to_network
|
|
.expect("To network not initialized");
|
|
let storage = self.storage.expect("Storage not initialized");
|
|
let ws_message_broadcast = self
|
|
.service_data
|
|
.ws_message_broadcast
|
|
.expect("WS message broadcast not initialized");
|
|
let api_listener = self.with_application.init.api_listener;
|
|
let shutdown_manager = self
|
|
.shutdown_manager
|
|
.expect("Shutdown manager not initialized");
|
|
let services = self.services;
|
|
|
|
Ephemera {
|
|
node_info,
|
|
block_manager,
|
|
broadcaster,
|
|
from_network,
|
|
to_network,
|
|
broadcast_group: BroadcastGroup::new(),
|
|
storage: Arc::new(Mutex::new(storage)),
|
|
ws_message_broadcast,
|
|
api_listener,
|
|
api_cmd_processor: ApiCmdProcessor::new(),
|
|
application: application.into(),
|
|
ephemera_handle,
|
|
shutdown_manager,
|
|
services,
|
|
}
|
|
}
|
|
}
|