From dec3ea075128df3450990f3184ced19a8e4d93ba Mon Sep 17 00:00:00 2001 From: Boyu Yang Date: Tue, 2 Apr 2024 16:27:56 +0800 Subject: [PATCH] feat: add subcommand to provide JSON-RPC APIs without updating any CKB cells --- src/cli/init.rs | 2 +- src/cli/mod.rs | 15 +++++++-- src/cli/watch.rs | 80 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 src/cli/watch.rs diff --git a/src/cli/init.rs b/src/cli/init.rs index 6401e75..97f2f7b 100644 --- a/src/cli/init.rs +++ b/src/cli/init.rs @@ -89,7 +89,7 @@ pub struct Args { /// Disable the on-chain difficulty check. /// - /// Warning + /// ### Warning /// /// For testing purpose only. /// Do NOT enable this flag in production environment. diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 217e5fe..fa2e143 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -15,6 +15,7 @@ mod deploy; mod init; mod serve; mod sync; +mod watch; #[derive(Parser)] #[command(author, version, about)] @@ -33,9 +34,17 @@ pub enum Commands { Deploy(deploy::Args), /// Initialize a new Bitcoin SPV instance on CKB, and initialize local storage. Init(init::Args), - /// Run a service to update a Bitcoin SPV instance base on local storage. + /// Run a service to update a Bitcoin SPV instance base on local storage, + /// and provide JSON-RPC APIs. + /// + /// If you don't want to update the Bitcoin SPV instance, try the subcommand `watch`. Serve(serve::Args), - /// Sync data to rebuild local storage base on an existed Bitcoin SPV instance. + /// Run a read-only service to provide JSON-RPC APIs, + /// without updating for the Bitcoin SPV instance. + /// + /// If you want to update the Bitcoin SPV instance, try the subcommand `serve`. + Watch(watch::Args), + /// Sync data to rebuild local storage base on an existed on-chain Bitcoin SPV instance. Sync(sync::Args), } @@ -121,6 +130,7 @@ impl Cli { Commands::Deploy(args) => args.execute()?, Commands::Init(args) => args.execute()?, Commands::Serve(args) => args.execute()?, + Commands::Watch(args) => args.execute()?, Commands::Sync(args) => args.execute()?, } log::info!("Bitcoin SPV on CKB service is stopped."); @@ -132,6 +142,7 @@ impl Cli { Commands::Deploy(ref args) => args.common.configure_logger(), Commands::Init(ref args) => args.common.configure_logger(), Commands::Serve(ref args) => args.common.configure_logger(), + Commands::Watch(ref args) => args.common.configure_logger(), Commands::Sync(ref args) => args.common.configure_logger(), } } diff --git a/src/cli/watch.rs b/src/cli/watch.rs new file mode 100644 index 0000000..5ba909b --- /dev/null +++ b/src/cli/watch.rs @@ -0,0 +1,80 @@ +//! The `watch` sub-command. + +use std::{net::SocketAddr, path::PathBuf, thread, time}; + +use clap::Parser; + +use crate::{ + components::{ApiServiceConfig, SpvService, Storage}, + prelude::*, + result::{Error, Result}, +}; + +#[derive(Parser)] +pub struct Args { + #[clap(flatten)] + pub(crate) common: super::CommonArgs, + + /// The directory, which stores all cached data. + #[arg(long)] + pub(crate) data_dir: PathBuf, + + #[clap(flatten)] + pub(crate) ckb: super::CkbRoArgs, + + #[clap(flatten)] + pub(crate) bitcoin: super::BitcoinArgs, + + /// The JSON-RPC server's listen address. + #[arg(long)] + pub(crate) listen_address: SocketAddr, + + /// A interval in seconds. + /// + /// - When no better bitcoin blocks, waiting for several seconds. + /// - After a CKB transaction is sent, waiting for several seconds. + #[arg(long, default_value = "30")] + pub(crate) interval: u64, + + /// The batch size that how many Bitcoin headers will be downloaded at once. + #[arg(long, default_value = "30")] + pub(crate) bitcoin_headers_download_batch_size: u32, +} + +impl Args { + pub fn execute(&self) -> Result<()> { + log::info!("Starting the Bitcoin SPV service (readonly)"); + + let storage = Storage::new(&self.data_dir)?; + if !storage.is_initialized()? { + let msg = format!( + "user-provided data directory \"{}\" is empty, please initialize it", + self.data_dir.display() + ); + return Err(Error::other(msg)); + } + let ckb_cli = self.ckb.client(); + let btc_cli = self.bitcoin.client(); + + let spv_service = SpvService { + ckb_cli: ckb_cli.clone(), + btc_cli: btc_cli.clone(), + storage: storage.clone(), + }; + + let _api_service = ApiServiceConfig::new(self.listen_address).start(spv_service.clone()); + + loop { + if !spv_service.sync_storage(self.bitcoin_headers_download_batch_size)? { + continue; + } + self.take_a_break(); + } + + // TODO Handle Ctrl-C and clean resources before exit. + } + + fn take_a_break(&self) { + thread::sleep(time::Duration::from_secs(self.interval)); + } +}