daemon: bitcoin: introduce the Bitcoin poller
This commit is contained in:
parent
1f35885087
commit
6997adc073
@ -477,4 +477,27 @@ impl BitcoinD {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn sync_progress(&self) -> f64 {
|
||||
// TODO: don't harass revaultd, be smarter like in revaultd.
|
||||
roundup_progress(
|
||||
self.make_node_request("getblockchaininfo", &[])
|
||||
.get("verificationprogress")
|
||||
.and_then(Json::as_f64)
|
||||
.expect("No valid 'verificationprogress' in getblockchaininfo response?"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Bitcoind uses a guess for the value of verificationprogress. It will eventually get to
|
||||
// be 1, and we want to be less conservative.
|
||||
fn roundup_progress(progress: f64) -> f64 {
|
||||
let precision = 10u64.pow(5) as f64;
|
||||
let progress_rounded = (progress * precision + 1.0) as u64;
|
||||
|
||||
if progress_rounded * 10 >= precision as u64 {
|
||||
1.0
|
||||
} else {
|
||||
(progress_rounded as f64 / precision) as f64
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,5 +2,19 @@
|
||||
///!
|
||||
///! Broadcast transactions, poll for new unspent coins, gather fee estimates.
|
||||
pub mod d;
|
||||
pub mod poller;
|
||||
|
||||
pub trait BitcoinInterface {}
|
||||
use std::sync;
|
||||
|
||||
/// Our Bitcoin backend.
|
||||
pub trait BitcoinInterface: Send {
|
||||
/// Get the progress of the block chain synchronization.
|
||||
/// Returns a percentage between 0 and 1.
|
||||
fn sync_progress(&self) -> f64;
|
||||
}
|
||||
|
||||
impl BitcoinInterface for sync::Arc<sync::RwLock<d::BitcoinD>> {
|
||||
fn sync_progress(&self) -> f64 {
|
||||
self.read().unwrap().sync_progress()
|
||||
}
|
||||
}
|
||||
|
||||
46
src/bitcoin/poller/looper.rs
Normal file
46
src/bitcoin/poller/looper.rs
Normal file
@ -0,0 +1,46 @@
|
||||
use crate::bitcoin::BitcoinInterface;
|
||||
|
||||
use std::{
|
||||
sync::{self, atomic},
|
||||
thread, time,
|
||||
};
|
||||
|
||||
/// Main event loop. Repeatedly polls the Bitcoin interface until told to stop through the
|
||||
/// `shutdown` atomic.
|
||||
pub fn looper(
|
||||
bit: impl BitcoinInterface,
|
||||
shutdown: sync::Arc<atomic::AtomicBool>,
|
||||
poll_interval: time::Duration,
|
||||
) {
|
||||
let mut last_poll = None;
|
||||
let mut synced = false;
|
||||
|
||||
while !shutdown.load(atomic::Ordering::Relaxed) || last_poll.is_none() {
|
||||
let now = time::Instant::now();
|
||||
|
||||
if let Some(last_poll) = last_poll {
|
||||
if now.duration_since(last_poll) < poll_interval {
|
||||
thread::sleep(time::Duration::from_millis(500));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
last_poll = Some(now);
|
||||
|
||||
// Don't poll until the Bitcoin backend is fully synced.
|
||||
if !synced {
|
||||
let sync_progress = bit.sync_progress();
|
||||
log::info!(
|
||||
"Block chain synchronization progress: {:.2}%",
|
||||
sync_progress
|
||||
);
|
||||
synced = sync_progress == 1.0;
|
||||
if !synced {
|
||||
// Avoid harassing bitcoind..
|
||||
// TODO: be smarter, like in revaultd, but more generic too.
|
||||
#[cfg(not(test))]
|
||||
thread::sleep(time::Duration::from_secs(30));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/bitcoin/poller/mod.rs
Normal file
31
src/bitcoin/poller/mod.rs
Normal file
@ -0,0 +1,31 @@
|
||||
mod looper;
|
||||
|
||||
use crate::bitcoin::{poller::looper::looper, BitcoinInterface};
|
||||
|
||||
use std::{
|
||||
sync::{self, atomic},
|
||||
thread, time,
|
||||
};
|
||||
|
||||
/// The Bitcoin poller handler.
|
||||
pub struct Poller {
|
||||
handle: thread::JoinHandle<()>,
|
||||
shutdown: sync::Arc<atomic::AtomicBool>,
|
||||
}
|
||||
|
||||
impl Poller {
|
||||
pub fn start(bit: impl BitcoinInterface + 'static, poll_interval: time::Duration) -> Poller {
|
||||
let shutdown = sync::Arc::from(atomic::AtomicBool::from(false));
|
||||
let handle = thread::spawn({
|
||||
let shutdown = shutdown.clone();
|
||||
move || looper(bit, shutdown, poll_interval)
|
||||
});
|
||||
|
||||
Poller { shutdown, handle }
|
||||
}
|
||||
|
||||
pub fn stop(self) {
|
||||
self.shutdown.store(true, atomic::Ordering::Relaxed);
|
||||
self.handle.join().expect("The poller loop must not fail");
|
||||
}
|
||||
}
|
||||
28
src/lib.rs
28
src/lib.rs
@ -5,12 +5,15 @@ mod daemonize;
|
||||
mod database;
|
||||
|
||||
use crate::{
|
||||
bitcoin::d::{BitcoinD, BitcoindError},
|
||||
bitcoin::{
|
||||
d::{BitcoinD, BitcoindError},
|
||||
poller,
|
||||
},
|
||||
config::{config_folder_path, Config},
|
||||
database::sqlite::{FreshDbOptions, SqliteDb, SqliteDbError},
|
||||
};
|
||||
|
||||
use std::{error, fmt, fs, io, path};
|
||||
use std::{error, fmt, fs, io, path, sync};
|
||||
|
||||
#[cfg(not(test))]
|
||||
use std::{panic, process};
|
||||
@ -173,7 +176,6 @@ impl DaemonHandle {
|
||||
}
|
||||
bitcoind.try_load_watchonly_wallet();
|
||||
bitcoind.sanity_check(&config.main_descriptor, config.bitcoind_config.network)?;
|
||||
bitcoind.with_retry_limit(None);
|
||||
log::info!("Connection to bitcoind established and checked.");
|
||||
|
||||
// If we are on a UNIX system and they told us to daemonize, do it now.
|
||||
@ -190,6 +192,12 @@ impl DaemonHandle {
|
||||
}
|
||||
}
|
||||
|
||||
// Spawn the bitcoind poller with a retry limit high enough that we'd fail after that.
|
||||
let bitcoind = sync::Arc::from(sync::RwLock::from(bitcoind.with_retry_limit(None)));
|
||||
let bit_poller =
|
||||
poller::Poller::start(bitcoind.clone(), config.bitcoind_config.poll_interval_secs);
|
||||
bit_poller.stop();
|
||||
|
||||
Ok(Self {})
|
||||
}
|
||||
|
||||
@ -340,6 +348,18 @@ mod tests {
|
||||
stream.flush().unwrap();
|
||||
}
|
||||
|
||||
// Send them a response to 'getblockchaininfo' saying we are far from being synced
|
||||
fn complete_sync_check<'a>(server: &net::TcpListener) {
|
||||
let net_resp = [
|
||||
"HTTP/1.1 200\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"verificationprogress\":0.1}}\n".as_bytes(),
|
||||
]
|
||||
.concat();
|
||||
let (mut stream, _) = server.accept().unwrap();
|
||||
read_til_json_end(&mut stream);
|
||||
stream.write_all(&net_resp).unwrap();
|
||||
stream.flush().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn daemon_startup() {
|
||||
let tmp_dir = env::temp_dir().join(format!(
|
||||
@ -410,6 +430,7 @@ mod tests {
|
||||
complete_network_check(&server);
|
||||
complete_wallet_check(&server, &wo_path);
|
||||
complete_desc_check(&server, desc_str);
|
||||
complete_sync_check(&server);
|
||||
daemon_thread.join().unwrap();
|
||||
|
||||
// The datadir is created now, so if we restart it it won't create the wo wallet.
|
||||
@ -423,6 +444,7 @@ mod tests {
|
||||
complete_network_check(&server);
|
||||
complete_wallet_check(&server, &wo_path);
|
||||
complete_desc_check(&server, desc_str);
|
||||
complete_sync_check(&server);
|
||||
daemon_thread.join().unwrap();
|
||||
|
||||
fs::remove_dir_all(&tmp_dir).unwrap();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user