extern crate rostrum;
extern crate anyhow;
#[macro_use]
extern crate log;
use anyhow::Result;
use std::{collections::HashSet, sync::Arc};
use rostrum::{
app::App,
cache::TransactionCache,
chain::Chain,
chaindef::Block,
config::Config,
daemon::Daemon,
discovery::{announcer::PeerAnnouncer, discoverer::PeerDiscoverer},
doslimit::{ConnectionLimits, GlobalLimits},
index::Index,
metrics::Metrics,
query::Query,
rpc::daemon::server::Server,
signal::SignalNotification,
signal::{NetworkNotifier, Waiter},
store::{is_compatible_version, DBContents, DBStore},
};
struct FlushRAII {
store: Arc<DBStore>,
}
impl Drop for FlushRAII {
fn drop(&mut self) {
info!("Flushing indexes to disk...");
if let Err(e) = self.store.flush() {
warn!("Flushing indexes failed: {e}");
return;
}
info!("Flushing indexes done.")
}
}
async fn run_server(config: Arc<Config>) -> Result<()> {
let network_notifier = Arc::new(NetworkNotifier::new());
let signal = Waiter::start(network_notifier.clone()).await;
let metrics = Arc::new(
Metrics::new(config.monitoring_addr, config.metrics).expect("Failed to start metrics"),
);
let daemon = Arc::new(Daemon::new(&config, network_notifier.clone(), &metrics).await?);
if config.reindex {
info!("Configuration flag `reindex` enabled. Running a full reindex.");
DBStore::destroy(&config.db_path);
}
let compatible = is_compatible_version(&config.db_path, &metrics);
if !compatible {
info!("Incompatible database. Running full reindex.");
DBStore::destroy(&config.db_path);
}
let store = Arc::new(
DBStore::open(
DBContents::ConfirmedIndex,
&config.db_path,
&metrics,
!compatible || config.reindex, )
.unwrap(),
);
let _flush_raii = FlushRAII {
store: Arc::clone(&store),
};
let genesis: Block = daemon
.get_genesis()
.await
.expect("Failed to get genesis block");
let genesis_hash = genesis.block_hash();
let index = Index::load(
store,
Chain::new(genesis),
daemon.clone(),
&metrics,
&config,
)
.await?;
let discoverer = PeerDiscoverer::new(&config, &metrics).await;
let announcer = Arc::new(PeerAnnouncer::new(genesis_hash, &config).await);
let app = App::new(
index,
daemon.clone(),
&config,
discoverer.clone(),
announcer.clone(),
)?;
let mempool = Arc::new({
let mut mempool_path = config.db_path.clone();
mempool_path.push("mempool");
rostrum::mempool::Tracker::new(&mempool_path, &metrics)
});
let tx_cache = TransactionCache::new(config.tx_cache_size as u64, &metrics);
let query = Query::new(
app.clone(),
&metrics,
tx_cache,
mempool.clone(),
config.network_type,
)?;
let connection_limits = ConnectionLimits::new(
config.rpc_timeout,
config.scripthash_subscription_limit,
config.scripthash_alias_bytes_limit,
);
let global_limits = Arc::new(GlobalLimits::new(
config.rpc_max_connections,
config.rpc_max_connections_shared_prefix,
&metrics,
));
app.first_update().await?;
let server = Server::start(
config.clone(),
query.clone(),
metrics.clone(),
connection_limits,
global_limits,
network_notifier,
)
.await;
announcer
.start_announce_thread(discoverer, &config, &metrics)
.await;
loop {
match signal.wait(config.wait_duration).await {
Ok(sig) => {
match sig {
SignalNotification::NewTxs(tx_invs) => {
let txs_changed = mempool.update_from_txids(tx_invs, query.tx()).await;
server
.notify_scripthash_subscriptions(&[], txs_changed)
.await;
continue;
}
SignalNotification::NewBlock(blockhash) => {
trace!("NewBlock notification");
if app.index().chain().contains(&blockhash).await {
continue;
}
}
SignalNotification::Timeout => {
daemon.p2p_keepalive().await;
}
SignalNotification::NewFullTx(tx) => {
let txid = tx.txid();
match mempool.update_from_tx(tx, query.tx()).await {
Ok(_) => {
server
.notify_scripthash_subscriptions(&[], HashSet::from([txid]))
.await;
}
Err(e) => {
trace!("Update from tx broadcast failed: {}", e);
}
}
continue;
}
}
}
Err(err) => {
info!("Stopping server: {}", err);
break;
}
}
let txs_changed = query.update_mempool().await?;
let (headers_changed, new_tip) = app.update_blocks().await?;
server
.notify_scripthash_subscriptions(&headers_changed, txs_changed)
.await;
if let Some(header) = new_tip {
server.notify_subscriptions_chaintip(header).await;
}
}
Ok(())
}
#[tokio::main]
async fn main() -> Result<()> {
let config = Arc::new(Config::from_args());
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
if let Err(e) = run_server(config).await {
info!(
"Server stopped: {}",
e.chain().map(|x| x.to_string()).collect::<String>()
);
if e.to_string().contains("Shutdown triggered")
|| e.to_string().contains("Interrupted by signal")
{
Ok(())
} else {
Err(e)
}
} else {
Ok(())
}
}