X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning-block-sync%2Fsrc%2Frest.rs;h=74a460a7ab505d24d70582dece828638c373e593;hb=2f734f97550014e5424e55523ed46d76b94b737d;hp=2ddfed7dad84b12dc43d5ac318b31b223ca74051;hpb=83595dbfdc9669e52b7095a6c2bc087a4398cc1d;p=rust-lightning diff --git a/lightning-block-sync/src/rest.rs b/lightning-block-sync/src/rest.rs index 2ddfed7d..74a460a7 100644 --- a/lightning-block-sync/src/rest.rs +++ b/lightning-block-sync/src/rest.rs @@ -1,22 +1,22 @@ //! Simple REST client implementation which implements [`BlockSource`] against a Bitcoin Core REST //! endpoint. -use crate::{BlockHeaderData, BlockSource, AsyncBlockSourceResult}; +use crate::{BlockData, BlockHeaderData, BlockSource, AsyncBlockSourceResult}; use crate::http::{BinaryResponse, HttpEndpoint, HttpClient, JsonResponse}; +use crate::gossip::UtxoSource; +use crate::convert::GetUtxosResponse; -use bitcoin::blockdata::block::Block; +use bitcoin::OutPoint; use bitcoin::hash_types::BlockHash; -use bitcoin::hashes::hex::ToHex; - -use futures::lock::Mutex; use std::convert::TryFrom; use std::convert::TryInto; +use std::sync::Mutex; /// A simple REST client for requesting resources using HTTP `GET`. pub struct RestClient { endpoint: HttpEndpoint, - client: Mutex, + client: Mutex>, } impl RestClient { @@ -24,8 +24,7 @@ impl RestClient { /// /// The endpoint should contain the REST path component (e.g., http://127.0.0.1:8332/rest). pub fn new(endpoint: HttpEndpoint) -> std::io::Result { - let client = Mutex::new(HttpClient::connect(&endpoint)?); - Ok(Self { endpoint, client }) + Ok(Self { endpoint, client: Mutex::new(None) }) } /// Requests a resource encoded in `F` format and interpreted as type `T`. @@ -33,22 +32,26 @@ impl RestClient { where F: TryFrom, Error = std::io::Error> + TryInto { let host = format!("{}:{}", self.endpoint.host(), self.endpoint.port()); let uri = format!("{}/{}", self.endpoint.path().trim_end_matches("/"), resource_path); - self.client.lock().await.get::(&uri, &host).await?.try_into() + let mut client = if let Some(client) = self.client.lock().unwrap().take() { client } + else { HttpClient::connect(&self.endpoint)? }; + let res = client.get::(&uri, &host).await?.try_into(); + *self.client.lock().unwrap() = Some(client); + res } } impl BlockSource for RestClient { fn get_header<'a>(&'a self, header_hash: &'a BlockHash, _height: Option) -> AsyncBlockSourceResult<'a, BlockHeaderData> { Box::pin(async move { - let resource_path = format!("headers/1/{}.json", header_hash.to_hex()); + let resource_path = format!("headers/1/{}.json", header_hash.to_string()); Ok(self.request_resource::(&resource_path).await?) }) } - fn get_block<'a>(&'a self, header_hash: &'a BlockHash) -> AsyncBlockSourceResult<'a, Block> { + fn get_block<'a>(&'a self, header_hash: &'a BlockHash) -> AsyncBlockSourceResult<'a, BlockData> { Box::pin(async move { - let resource_path = format!("block/{}.bin", header_hash.to_hex()); - Ok(self.request_resource::(&resource_path).await?) + let resource_path = format!("block/{}.bin", header_hash.to_string()); + Ok(BlockData::FullBlock(self.request_resource::(&resource_path).await?)) }) } @@ -59,11 +62,30 @@ impl BlockSource for RestClient { } } +impl UtxoSource for RestClient { + fn get_block_hash_by_height<'a>(&'a self, block_height: u32) -> AsyncBlockSourceResult<'a, BlockHash> { + Box::pin(async move { + let resource_path = format!("blockhashbyheight/{}.bin", block_height); + Ok(self.request_resource::(&resource_path).await?) + }) + } + + fn is_output_unspent<'a>(&'a self, outpoint: OutPoint) -> AsyncBlockSourceResult<'a, bool> { + Box::pin(async move { + let resource_path = format!("getutxos/{}-{}.json", outpoint.txid.to_string(), outpoint.vout); + let utxo_result = + self.request_resource::(&resource_path).await?; + Ok(utxo_result.hit_bitmap_nonempty) + }) + } +} + #[cfg(test)] mod tests { use super::*; use crate::http::BinaryResponse; use crate::http::client_tests::{HttpServer, MessageBody}; + use bitcoin::hashes::Hash; /// Parses binary data as a string-encoded `u32`. impl TryInto for BinaryResponse { @@ -112,4 +134,32 @@ mod tests { Ok(n) => assert_eq!(n, 42), } } + + #[tokio::test] + async fn parses_negative_getutxos() { + let server = HttpServer::responding_with_ok(MessageBody::Content( + // A real response contains a few more fields, but we actually only look at the + // "bitmap" field, so this should suffice for testing + "{\"chainHeight\": 1, \"bitmap\":\"0\",\"utxos\":[]}" + )); + let client = RestClient::new(server.endpoint()).unwrap(); + + let outpoint = OutPoint::new(bitcoin::Txid::from_byte_array([0; 32]), 0); + let unspent_output = client.is_output_unspent(outpoint).await.unwrap(); + assert_eq!(unspent_output, false); + } + + #[tokio::test] + async fn parses_positive_getutxos() { + let server = HttpServer::responding_with_ok(MessageBody::Content( + // A real response contains lots more data, but we actually only look at the "bitmap" + // field, so this should suffice for testing + "{\"chainHeight\": 1, \"bitmap\":\"1\",\"utxos\":[]}" + )); + let client = RestClient::new(server.endpoint()).unwrap(); + + let outpoint = OutPoint::new(bitcoin::Txid::from_byte_array([0; 32]), 0); + let unspent_output = client.is_output_unspent(outpoint).await.unwrap(); + assert_eq!(unspent_output, true); + } }