Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support JSON-RPC verifier #67

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<u16>()
.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,
Expand Down
80 changes: 65 additions & 15 deletions src/verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
Comment on lines +21 to +24
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use a type parameter instead of introducing an enum?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but it bleeds into the entire application and felt unnecessary when RGS is a binary and not really a lib. But I can go back and do this if you think it's better once we resolve the GossipVerifier issues.


impl BlockSource for AnyChainSource {
fn get_header<'a>(&'a self, header_hash: &'a BlockHash, height_hint: Option<u32>) -> 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<u32>)> {
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<L: Deref + Clone + Send + Sync + 'static> where L::Target: Logger {
rest_client: Arc<RestClient>,
chain_source: Arc<AnyChainSource>,
graph: Arc<NetworkGraph<L>>,
outbound_gossiper: Arc<P2PGossipSync<Arc<NetworkGraph<L>>, Arc<Self>, L>>,
peer_handler: Mutex<Option<GossipPeerManager<L>>>,
Expand All @@ -29,8 +74,17 @@ struct RestBinaryResponse(Vec<u8>);

impl<L: Deref + Clone + Send + Sync + 'static> ChainVerifier<L> where L::Target: Logger {
pub(crate) fn new(graph: Arc<NetworkGraph<L>>, outbound_gossiper: Arc<P2PGossipSync<Arc<NetworkGraph<L>>, Arc<Self>, 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),
Expand All @@ -41,12 +95,12 @@ impl<L: Deref + Clone + Send + Sync + 'static> ChainVerifier<L> where L::Target:
*self.peer_handler.lock().unwrap() = Some(peer_handler);
}

async fn retrieve_utxo(client: Arc<RestClient>, short_channel_id: u64, logger: L) -> Result<TxOut, UtxoLookupError> {
async fn retrieve_utxo(chain_source: Arc<AnyChainSource>, short_channel_id: u64, logger: L) -> Result<TxOut, UtxoLookupError> {
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);
Expand All @@ -59,17 +113,13 @@ impl<L: Deref + Clone + Send + Sync + 'static> ChainVerifier<L> where L::Target:
Ok(transaction.output.swap_remove(output_index as usize))
}

async fn retrieve_block(client: Arc<RestClient>, block_height: u32, logger: L) -> Result<Block, UtxoLookupError> {
let uri = format!("blockhashbyheight/{}.bin", block_height);
let block_hash_result =
client.request_resource::<BinaryResponse, RestBinaryResponse>(&uri).await;
let block_hash: Vec<u8> = 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<AnyChainSource>, block_height: u32, logger: L) -> Result<Block, UtxoLookupError> {
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)
Expand All @@ -88,7 +138,7 @@ impl<L: Deref + Clone + Send + Sync + 'static> UtxoLookup for ChainVerifier<L> 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();
Expand Down
Loading