use std::future::Future;
use std::net::SocketAddr;
use std::sync::Arc;
use crate::fetch::Client;
use crate::server::preload::headers;
use crate::store::memory::MemoryStore;
use crate::store::AnyStore;
use crate::threads::blocks::blocks_infallible;
use crate::threads::mempool::mempool_sync_infallible;
use age::x25519::Identity;
use bitcoin::{NetworkKind, PrivateKey};
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper_util::rt::TokioIo;
use route::infallible_route;
use tokio::net::TcpListener;
use tokio::sync::Mutex;
mod derivation_cache;
pub mod encryption;
mod mempool;
pub mod preload;
pub mod route;
pub mod sign;
mod state;
pub use mempool::Mempool;
pub use state::State;
#[derive(Clone, clap::ValueEnum, Debug, PartialEq, Eq, Copy)]
pub enum Network {
Liquid,
LiquidTestnet,
ElementsRegtest,
Bitcoin,
BitcoinTestnet,
BitcoinRegtest,
BitcoinSignet,
}
#[derive(clap::Parser, Clone, Default)]
#[command(author, version, about, long_about = None)]
pub struct Arguments {
#[arg(env, long)]
pub network: Network,
#[arg(env, long)]
pub use_esplora: bool,
#[arg(env, long)]
pub esplora_url: Option<String>,
#[arg(env, long)]
pub node_url: Option<String>,
#[arg(env, long)]
pub listen: Option<SocketAddr>,
#[cfg(feature = "db")]
#[arg(env, long)]
pub db_dir: Option<std::path::PathBuf>,
#[arg(long, env)]
pub server_key: Option<Identity>,
#[arg(long, env)]
pub wif_key: Option<PrivateKey>,
#[arg(long, env)]
pub rpc_user_password: Option<String>,
#[arg(env, long, default_value = "100")]
pub max_addresses: usize,
#[arg(env, long)]
pub add_cors: bool,
#[arg(env, long, default_value = "1000000")]
pub derivation_cache_capacity: usize,
#[arg(env, long, default_value = "120")]
pub logs_rocksdb_stat_every: u64,
#[arg(env, long)]
pub do_compaction: bool,
#[arg(env, long, default_value = "64")]
pub shared_db_cache_mb: u64,
#[arg(env, long)]
pub enable_db_statistics: bool,
}
impl Arguments {
pub fn is_valid(&self) -> Result<(), Error> {
if !self.use_esplora && self.rpc_user_password.is_none() {
Err(Error::String(
"When using the node you must specify --rpc-user-password".to_string(),
))
} else {
Ok(())
}
}
}
impl std::str::FromStr for Network {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"liquid" => Ok(Self::Liquid),
"liquid-testnet" => Ok(Self::LiquidTestnet),
"elements-regtest" => Ok(Self::ElementsRegtest),
_ => Err(Error::String(format!("Invalid network: {}", s))),
}
}
}
impl std::fmt::Display for Network {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Network::Liquid => "liquid",
Network::LiquidTestnet => "liquid-testnet",
Network::ElementsRegtest { .. } => "elements-regtest",
Network::Bitcoin => "bitcoin",
Network::BitcoinTestnet => "bitcoin-testnet",
Network::BitcoinRegtest => "bitcoin-regtest",
Network::BitcoinSignet => "bitcoin-signet",
};
write!(f, "{}", s)
}
}
impl Default for Network {
fn default() -> Self {
Self::Liquid
}
}
impl Network {
pub fn as_network_kind(&self) -> NetworkKind {
match self {
Network::Liquid => NetworkKind::Main,
_ => NetworkKind::Test,
}
}
pub fn default_node_listen_port(&self) -> u16 {
match self {
Network::Liquid => 7041,
Network::LiquidTestnet => 7039,
Network::ElementsRegtest => 7043, Network::Bitcoin => 8332,
Network::BitcoinTestnet => 18332,
Network::BitcoinRegtest => 18443,
Network::BitcoinSignet => 38332,
}
}
pub fn default_listen_port(&self) -> u16 {
match self {
Network::Liquid => 3100,
Network::LiquidTestnet => 3101,
Network::ElementsRegtest => 3102,
Network::Bitcoin => 3103,
Network::BitcoinTestnet => 3104,
Network::BitcoinRegtest => 3105,
Network::BitcoinSignet => 3106,
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
WrongNetwork,
Other,
CannotParseHeight,
InvalidTxid,
CannotFindTx,
InvalidBlockHash,
CannotFindBlockHeader,
DBOpen(String),
CannotLoadEncryptionKey,
CannotDecrypt,
CannotEncrypt,
InvalidTx,
String(String),
InvalidAddress(String),
CannotSpecifyBothDescriptorAndAddresses,
AtLeastOneFieldMandatory,
NotYetImplemented,
AddressCannotBeBlinded,
TooManyAddresses,
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl std::error::Error for Error {}
#[cfg(not(feature = "db"))]
fn get_store(_args: &Arguments) -> Result<AnyStore, Error> {
Ok(AnyStore::Mem(MemoryStore::new()))
}
#[cfg(feature = "db")]
fn get_store(args: &Arguments) -> Result<AnyStore, Error> {
use crate::store;
Ok(match args.db_dir.as_ref() {
Some(p) => {
let mut path = p.clone();
path.push("db");
path.push(args.network.to_string());
let db_store =
store::db::DBStore::open(&path, args.shared_db_cache_mb, args.enable_db_statistics)
.map_err(|e| Error::DBOpen(format!("{e:?}")))?;
if args.do_compaction {
db_store
.compact_database()
.map_err(|e| Error::DBOpen(format!("Compaction failed: {e:?}")))?;
}
AnyStore::Db(db_store)
}
None => AnyStore::Mem(MemoryStore::new()),
})
}
pub async fn inner_main(
args: Arguments,
shutdown_signal: impl Future<Output = ()>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
log::info!("starting waterfalls");
args.is_valid()?;
let store = get_store(&args)?;
let key = args.server_key.clone().unwrap_or_else(Identity::generate);
let network_kind = args.network.as_network_kind();
if let Some(wif_key) = args.wif_key.as_ref() {
if wif_key.network != network_kind {
panic!(
"WIF key network {:?} does not match network kind {:?}",
wif_key.network, network_kind
);
}
}
let wif_key = args
.wif_key
.unwrap_or_else(|| PrivateKey::generate(network_kind));
let state = Arc::new(State::new(
store,
key,
wif_key,
args.max_addresses,
args.derivation_cache_capacity,
)?);
{
let state = state.clone();
headers(state).await.unwrap();
}
let (initial_sync_tx, initial_sync_rx) = tokio::sync::oneshot::channel::<()>();
let (shutdown_tx, _) = tokio::sync::broadcast::channel::<()>(1);
let h1 = {
let state = state.clone();
let client: Client = Client::new(&args);
let shutdown_rx = shutdown_tx.subscribe();
tokio::spawn(async move {
let shutdown_future = async {
let mut rx = shutdown_rx;
let _ = rx.recv().await;
};
blocks_infallible(
state,
client,
args.network.into(),
initial_sync_tx,
shutdown_future,
args.logs_rocksdb_stat_every,
)
.await
})
};
let h2 = {
let state = state.clone();
let client = Client::new(&args);
let shutdown_rx = shutdown_tx.subscribe();
tokio::spawn(async move {
let shutdown_future = async {
let mut rx = shutdown_rx;
let _ = rx.recv().await;
};
mempool_sync_infallible(
state,
client,
args.network.into(),
initial_sync_rx,
shutdown_future,
)
.await
})
};
let addr = args.listen.unwrap_or(SocketAddr::from((
[127, 0, 0, 1],
args.network.default_listen_port(),
)));
log::info!("Starting on http://{addr}");
let listener = TcpListener::bind(addr).await?;
let client = Client::new(&args);
let client = Arc::new(Mutex::new(client));
let mut signal = std::pin::pin!(shutdown_signal);
loop {
tokio::select! {
Ok( (stream, _)) = listener.accept() => {
let io = TokioIo::new(stream);
let state = state.clone();
let client = client.clone();
tokio::task::spawn(async move {
let state = &state;
let network = args.network;
let add_cors = args.add_cors;
let client = &client;
let service = service_fn(move |req| infallible_route(state, client, req, network, add_cors));
if let Err(err) = http1::Builder::new().serve_connection(io, service).await {
log::error!("Error serving connection: {:?}", err);
}
});
},
_ = &mut signal => {
log::info!("graceful shutdown signal received");
let _ = shutdown_tx.send(());
break;
}
}
}
h1.await.unwrap();
h2.await.unwrap();
log::info!("shutting down gracefully");
Ok(())
}