diff --git a/internal/config_specification.toml b/internal/config_specification.toml index 9738e1f..83d0349 100644 --- a/internal/config_specification.toml +++ b/internal/config_specification.toml @@ -80,6 +80,12 @@ type = "u64" doc = "Duration to wait between bitcoind polling" default = "10" +[[param]] +name = "jsonrpc_timeout_secs" +type = "u64" +doc = "Duration to wait until bitcoind JSON-RPC timeouts (must be greater than wait_duration_secs)." +default = "15" + [[param]] name = "index_batch_size" type = "usize" diff --git a/src/config.rs b/src/config.rs index e86fe5f..d7e330b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -130,6 +130,7 @@ pub struct Config { pub electrum_rpc_addr: SocketAddr, pub monitoring_addr: SocketAddr, pub wait_duration: Duration, + pub jsonrpc_timeout: Duration, pub index_batch_size: usize, pub index_lookup_limit: Option, pub auto_reindex: bool, @@ -283,6 +284,15 @@ impl Config { 0 => None, _ => Some(config.index_lookup_limit), }; + + if config.jsonrpc_timeout_secs <= config.wait_duration_secs { + eprintln!( + "Error: jsonrpc_timeout_secs ({}) must be higher than wait_duration_secs ({})", + config.jsonrpc_timeout_secs, config.wait_duration_secs + ); + std::process::exit(1); + } + let config = Config { network: config.network, db_path: config.db_dir, @@ -293,6 +303,7 @@ impl Config { electrum_rpc_addr, monitoring_addr, wait_duration: Duration::from_secs(config.wait_duration_secs), + jsonrpc_timeout: Duration::from_secs(config.jsonrpc_timeout_secs), index_batch_size: config.index_batch_size, index_lookup_limit, auto_reindex: config.auto_reindex, diff --git a/src/daemon.rs b/src/daemon.rs index 45661c1..d8ace51 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -3,10 +3,14 @@ use anyhow::{Context, Result}; use bitcoin::{ consensus::serialize, hashes::hex::ToHex, Amount, Block, BlockHash, Transaction, Txid, }; -use core_rpc::{json, Auth, Client, RpcApi}; +use core_rpc::{json, jsonrpc, Auth, Client, RpcApi}; use parking_lot::Mutex; use serde_json::{json, Value}; +use std::fs::File; +use std::io::Read; +use std::path::Path; + use crate::{ chain::{Chain, NewHeader}, config::Config, @@ -48,16 +52,42 @@ fn rpc_poll(client: &mut Client) -> PollResult { } } +fn read_cookie(path: &Path) -> Result<(String, String)> { + // Load username and password from bitcoind cookie file: + // * https://github.com/bitcoin/bitcoin/pull/6388/commits/71cbeaad9a929ba6a7b62d9b37a09b214ae00c1a + // * https://bitcoin.stackexchange.com/questions/46782/rpc-cookie-authentication + let mut file = File::open(path) + .with_context(|| format!("failed to open bitcoind cookie file: {}", path.display()))?; + let mut contents = String::new(); + file.read_to_string(&mut contents) + .with_context(|| format!("failed to read bitcoind cookie from {}", path.display()))?; + + let parts: Vec<&str> = contents.splitn(2, ':').collect(); + ensure!( + parts.len() == 2, + "failed to parse bitcoind cookie - missing ':' separator" + ); + Ok((parts[0].to_owned(), parts[1].to_owned())) +} + pub(crate) fn rpc_connect(config: &Config) -> Result { let rpc_url = format!("http://{}", config.daemon_rpc_addr); - let auth = config.daemon_auth.get_auth(); - if let Auth::CookieFile(ref path) = auth { - if !path.exists() { - bail!("{:?} is missing - is bitcoind running?", path); - } - } - let mut client = Client::new(&rpc_url, auth) - .with_context(|| format!("failed to connect to RPC: {}", config.daemon_rpc_addr))?; + let mut client = { + // Allow `wait_for_new_block` to take a bit longer before timing out. + // See https://github.com/romanz/electrs/issues/495 for more details. + let builder = jsonrpc::simple_http::SimpleHttpTransport::builder() + .url(&rpc_url)? + .timeout(config.jsonrpc_timeout); + let builder = match config.daemon_auth.get_auth() { + Auth::None => builder, + Auth::UserPass(user, pass) => builder.auth(user, Some(pass)), + Auth::CookieFile(path) => { + let (user, pass) = read_cookie(&path)?; + builder.auth(user, Some(pass)) + } + }; + Client::from_jsonrpc(jsonrpc::Client::with_transport(builder.build())) + }; loop { match rpc_poll(&mut client) {