Skip to content

Commit

Permalink
blockbook: catch version discrepancy in client initialization
Browse files Browse the repository at this point in the history
This will alert the user to a potential version discrepancy thereby
eliminating the need for associated debugging.

Note: We ignore the version check in `test_status()` and
`test_info_ws()` as this is now already done in the initialization of
the Blockbook clients. Like so, the diff will stay smaller in a
potential future version bump while maintaining the same test
coverage.
  • Loading branch information
bachmannscode committed Nov 16, 2023
1 parent d6d4a60 commit 1fd21d1
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 26 deletions.
40 changes: 31 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
//! ```ignore
//! # tokio_test::block_on(async {
//! # let url = format!("https://{}", std::env::var("BLOCKBOOK_SERVER").unwrap()).parse().unwrap();
//! let client = blockbook::Client::new(url);
//! let client = blockbook::Client::new(url).await?;
//!
//! // query the Genesis block hash
//! let genesis_hash = client
Expand Down Expand Up @@ -81,6 +81,12 @@ pub enum Error {
/// An error while parsing a URL.
#[error("invalid url: {0}")]
UrlError(#[from] url::ParseError),
/// The Blockbook version run on the provided server does not match the version required.
#[error("Blockbook version {client} required but server runs {server}.")]
VersionMismatch {
client: semver::Version,
server: semver::Version,
},
}

type Result<T> = std::result::Result<T, Error>;
Expand All @@ -103,18 +109,34 @@ impl Client {
/// Constructs a new client for a given server `base_url`.
///
/// `base_url` should not contain the `/api/v2/` path fragment.
pub fn new(base_url: url::Url) -> Self {
///
/// # Errors
///
/// If the server status could not be retreived or the Blockbook
/// version run on the server does not match the version required.
pub async fn new(base_url: url::Url) -> Result<Self> {
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
reqwest::header::CONTENT_TYPE,
reqwest::header::HeaderValue::from_static("application/json"),
);
let client = reqwest::Client::builder()
.default_headers(headers)
.timeout(std::time::Duration::from_secs(10))
.build()
.unwrap();
Self { base_url, client }
let client = Self {
base_url,
client: reqwest::Client::builder()
.default_headers(headers)
.timeout(std::time::Duration::from_secs(10))
.build()
.unwrap(),
};
let client_version = semver::Version::new(0, 4, 0);
let server_version = client.status().await?.blockbook.version;
if server_version != client_version {
return Err(Error::VersionMismatch {
client: client_version,
server: server_version,
});
}
Ok(client)
}

fn url(&self, endpoint: impl AsRef<str>) -> Result<url::Url> {
Expand Down Expand Up @@ -477,7 +499,7 @@ impl Client {
/// ```no_run
/// # tokio_test::block_on(async {
/// # let raw_tx = vec![0_u8];
/// # let client = blockbook::Client::new("dummy:".parse().unwrap());
/// # let client = blockbook::Client::new("dummy:".parse().unwrap()).await?;
/// // Assuming you have a hex serialization of a transaction:
/// // let raw_tx = hex::decode(raw_tx_hex).unwrap();
/// let tx: bitcoin::Transaction = bitcoin::consensus::deserialize(&raw_tx).unwrap();
Expand Down
23 changes: 20 additions & 3 deletions src/websocket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ pub enum Error {
/// A WebSocket error.
#[error("the websocket connection experienced a fatal error: {0:?}\nreinstantiate the client to reconnect")]
Websocket(#[from] std::sync::Arc<tokio_tungstenite::tungstenite::Error>),
/// The Blockbook version run on the provided server does not match the version required.
#[error("Blockbook version {client} required but server runs {server}.")]
VersionMismatch {
client: semver::Version,
server: semver::Version,
},
}

type Result<T> = std::result::Result<T, Error>;
Expand Down Expand Up @@ -193,18 +199,29 @@ impl Client {
///
/// # Errors
///
/// If the WebSocket connection could not be established.
/// If the WebSocket connection could not be established, the
/// server info could not be retreived or the Blockbook version
/// run on the server does not match the version required.
pub async fn new(url: url::Url) -> Result<Self> {
let stream = tokio_tungstenite::connect_async(url)
.await
.map_err(std::sync::Arc::new)?;
let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
let (job_tx, job_rx) = futures::channel::mpsc::channel(10);
tokio::spawn(Self::process(stream.0, job_rx, shutdown_rx));
Ok(Self {
let mut client = Self {
jobs: job_tx,
shutdown: Some(shutdown_tx),
})
};
let client_version = semver::Version::new(0, 4, 0);
let server_version = client.info().await?.version;
if server_version != client_version {
return Err(Error::VersionMismatch {
client: client_version,
server: server_version,
});
}
Ok(client)
}

#[allow(clippy::too_many_lines)]
Expand Down
Loading

0 comments on commit 1fd21d1

Please sign in to comment.