diff --git a/Cargo.toml b/Cargo.toml index f6d0515..9baf7db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] bitcoin = "0.29" lightning = { version = "0.0.117" } -lightning-block-sync = { version = "0.0.117", features=["rest-client"] } +lightning-block-sync = { version = "0.0.117", features=["rest-client", "rpc-client"] } lightning-net-tokio = { version = "0.0.117" } tokio = { version = "1.25", features = ["full"] } tokio-postgres = { version = "=0.7.5" } diff --git a/README.md b/README.md index 1210490..924d5dd 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,10 @@ can be made by setting environment variables, whose usage is as follows: | BITCOIN_REST_DOMAIN | 127.0.0.1 | Domain of the [bitcoind REST server](https://github.com/bitcoin/bitcoin/blob/master/doc/REST-interface.md) | | BITCOIN_REST_PORT | 8332 | HTTP port of the bitcoind REST server | | BITCOIN_REST_PATH | /rest/ | Path infix to access the bitcoind REST endpoints | +| USE_BITCOIN_RPC_API | false | Use JSON-RPC api instead of REST | +| BITCOIN_RPC_DOMAIN | 127.0.0.1 | Domain of the [bitcoind JSON-RPC server](https://github.com/bitcoin/bitcoin/blob/master/doc/JSON-RPC-interface.md) | +| BITCOIN_RPC_PORT | 8332 | port of the bitcoind JSON-RPC server | +| BITCOIN_RPC_PATH | / | Path infix to access the bitcoind JSON-RPC endpoints | | LN_PEERS | _Wallet of Satoshi_ | Comma separated list of LN peers to use for retrieving gossip | ### downloader diff --git a/src/config.rs b/src/config.rs index 026e09e..211b806 100644 --- a/src/config.rs +++ b/src/config.rs @@ -102,6 +102,26 @@ pub(crate) fn bitcoin_rest_endpoint() -> HttpEndpoint { HttpEndpoint::for_host(host).with_port(port).with_path(path) } +pub(crate) fn bitcoin_rpc_endpoint() -> HttpEndpoint { + let host = env::var("BITCOIN_RPC_DOMAIN").unwrap_or("127.0.0.1".to_string()); + let port = env::var("BITCOIN_RPC_PORT") + .unwrap_or("8332".to_string()) + .parse::() + .expect("BITCOIN_RPC_PORT env variable must be a u16."); + let path = env::var("BITCOIN_RPC_PATH").unwrap_or("/".to_string()); + HttpEndpoint::for_host(host).with_port(port).with_path(path) +} + +pub(crate) fn bitcoin_rpc_credentials() -> String { + let user = env::var("BITCOIN_RPC_USER").unwrap_or("user".to_string()); + let password = env::var("BITCOIN_RPC_PASSWORD").unwrap_or("password".to_string()); + format!("{}:{}", user, password) +} + +pub(crate) fn use_bitcoin_rpc_api() -> bool { + env::var("USE_BITCOIN_RPC_API").unwrap_or("false".to_string()) == "true" +} + pub(crate) fn db_config_table_creation_query() -> &'static str { "CREATE TABLE IF NOT EXISTS config ( id SERIAL PRIMARY KEY, diff --git a/src/verifier.rs b/src/verifier.rs index 6813ff7..b28c44b 100644 --- a/src/verifier.rs +++ b/src/verifier.rs @@ -5,20 +5,65 @@ use std::sync::Mutex; use bitcoin::{BlockHash, TxOut}; use bitcoin::blockdata::block::Block; -use bitcoin::hashes::Hash; use lightning::log_error; use lightning::routing::gossip::{NetworkGraph, P2PGossipSync}; use lightning::routing::utxo::{UtxoFuture, UtxoLookup, UtxoResult, UtxoLookupError}; use lightning::util::logger::Logger; use lightning_block_sync::{BlockData, BlockSource}; +use lightning_block_sync::gossip::UtxoSource; use lightning_block_sync::http::BinaryResponse; use lightning_block_sync::rest::RestClient; +use lightning_block_sync::rpc::RpcClient; use crate::config; use crate::types::GossipPeerManager; +enum AnyChainSource { + Rest(RestClient), + Rpc(RpcClient), +} + +impl BlockSource for AnyChainSource { + fn get_header<'a>(&'a self, header_hash: &'a BlockHash, height_hint: Option) -> lightning_block_sync::AsyncBlockSourceResult<'a, lightning_block_sync::BlockHeaderData> { + match self { + AnyChainSource::Rest(client) => client.get_header(header_hash, height_hint), + AnyChainSource::Rpc(client) => client.get_header(header_hash, height_hint), + } + } + + fn get_block<'a>(&'a self, header_hash: &'a BlockHash) -> lightning_block_sync::AsyncBlockSourceResult<'a, BlockData> { + match self { + AnyChainSource::Rest(client) => client.get_block(header_hash), + AnyChainSource::Rpc(client) => client.get_block(header_hash), + } + } + + fn get_best_block<'a>(&'a self) -> lightning_block_sync::AsyncBlockSourceResult<(BlockHash, Option)> { + match self { + AnyChainSource::Rest(client) => client.get_best_block(), + AnyChainSource::Rpc(client) => client.get_best_block(), + } + } +} + +impl UtxoSource for AnyChainSource { + fn get_block_hash_by_height<'a>(&'a self, block_height: u32) -> lightning_block_sync::AsyncBlockSourceResult<'a, BlockHash> { + match self { + AnyChainSource::Rest(client) => client.get_block_hash_by_height(block_height), + AnyChainSource::Rpc(client) => client.get_block_hash_by_height(block_height), + } + } + + fn is_output_unspent<'a>(&'a self, outpoint: bitcoin::OutPoint) -> lightning_block_sync::AsyncBlockSourceResult<'a, bool> { + match self { + AnyChainSource::Rest(client) => client.is_output_unspent(outpoint), + AnyChainSource::Rpc(client) => client.is_output_unspent(outpoint), + } + } +} + pub(crate) struct ChainVerifier where L::Target: Logger { - rest_client: Arc, + chain_source: Arc, graph: Arc>, outbound_gossiper: Arc>, Arc, L>>, peer_handler: Mutex>>, @@ -29,8 +74,17 @@ struct RestBinaryResponse(Vec); impl ChainVerifier where L::Target: Logger { pub(crate) fn new(graph: Arc>, outbound_gossiper: Arc>, Arc, L>>, logger: L) -> Self { + + let chain_source = { + if config::use_bitcoin_rpc_api() { + Arc::new(AnyChainSource::Rpc(RpcClient::new(&config::bitcoin_rpc_credentials(), config::bitcoin_rpc_endpoint()).unwrap())) + } else { + Arc::new(AnyChainSource::Rest(RestClient::new(config::bitcoin_rest_endpoint()).unwrap())) + } + }; + ChainVerifier { - rest_client: Arc::new(RestClient::new(config::bitcoin_rest_endpoint()).unwrap()), + chain_source, outbound_gossiper, graph, peer_handler: Mutex::new(None), @@ -41,12 +95,12 @@ impl ChainVerifier where L::Target: *self.peer_handler.lock().unwrap() = Some(peer_handler); } - async fn retrieve_utxo(client: Arc, short_channel_id: u64, logger: L) -> Result { + async fn retrieve_utxo(chain_source: Arc, short_channel_id: u64, logger: L) -> Result { let block_height = (short_channel_id >> 5 * 8) as u32; // block height is most significant three bytes let transaction_index = ((short_channel_id >> 2 * 8) & 0xffffff) as u32; let output_index = (short_channel_id & 0xffff) as u16; - let mut block = Self::retrieve_block(client, block_height, logger.clone()).await?; + let mut block = Self::retrieve_block(chain_source, block_height, logger.clone()).await?; if transaction_index as usize >= block.txdata.len() { log_error!(logger, "Could't find transaction {} in block {}", transaction_index, block_height); return Err(UtxoLookupError::UnknownTx); @@ -59,17 +113,13 @@ impl ChainVerifier where L::Target: Ok(transaction.output.swap_remove(output_index as usize)) } - async fn retrieve_block(client: Arc, block_height: u32, logger: L) -> Result { - let uri = format!("blockhashbyheight/{}.bin", block_height); - let block_hash_result = - client.request_resource::(&uri).await; - let block_hash: Vec = block_hash_result.map_err(|error| { - log_error!(logger, "Could't find block hash at height {}: {}", block_height, error.to_string()); + async fn retrieve_block(chain_source: Arc, block_height: u32, logger: L) -> Result { + let block_hash = chain_source.get_block_hash_by_height(block_height).await.map_err(|error| { + log_error!(logger, "Could't find block hash at height {}: {:?}", block_height, error); UtxoLookupError::UnknownChain - })?.0; - let block_hash = BlockHash::from_slice(&block_hash).unwrap(); + })?; - let block_result = client.get_block(&block_hash).await; + let block_result = chain_source.get_block(&block_hash).await; match block_result { Ok(BlockData::FullBlock(block)) => { Ok(block) @@ -88,7 +138,7 @@ impl UtxoLookup for ChainVerifier w let res = UtxoFuture::new(); let fut = res.clone(); let graph_ref = Arc::clone(&self.graph); - let client_ref = Arc::clone(&self.rest_client); + let client_ref = Arc::clone(&self.chain_source); let gossip_ref = Arc::clone(&self.outbound_gossiper); let pm_ref = self.peer_handler.lock().unwrap().clone(); let logger_ref = self.logger.clone();