rust-teos/teos/src/bitcoin_cli.rs
2023-02-06 22:56:09 +01:00

160 lines
5.8 KiB
Rust

//! Logic related to the BitcoindClient, an simple bitcoind client implementation.
// This is an adaptation of a bitcoind client with the minimal functionality required by the tower
// The original piece of software can be found at https://github.com/lightningdevkit/ldk-sample/blob/main/src/bitcoind_client.rs
/* This file is licensed under either of
* Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) or
* MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
* at your option.
*/
use std::convert::TryInto;
use std::io::{Error, ErrorKind};
use std::sync::Arc;
use tokio::sync::Mutex;
use bitcoin::base64;
use bitcoin::hash_types::{BlockHash, Txid};
use bitcoin::hashes::hex::ToHex;
use bitcoin::{Block, Transaction};
use lightning::util::ser::Writeable;
use lightning_block_sync::http::{HttpEndpoint, JsonResponse};
use lightning_block_sync::rpc::RpcClient;
use lightning_block_sync::{AsyncBlockSourceResult, BlockHeaderData, BlockSource};
/// A simple implementation of a bitcoind client (`bitcoin-cli`) with the minimal functionality required by the tower.
pub struct BitcoindClient<'a> {
/// The underlying RPC client.
bitcoind_rpc_client: Arc<Mutex<RpcClient>>,
/// The hostname to connect to.
host: &'a str,
/// The port to connect to.
port: u16,
/// The RPC user `bitcoind` is configured with.
rpc_user: &'a str,
/// The RPC password for the given user.
rpc_password: &'a str,
}
impl BlockSource for &BitcoindClient<'_> {
/// Gets a block header given its hash.
fn get_header<'a>(
&'a self,
header_hash: &'a BlockHash,
height_hint: Option<u32>,
) -> AsyncBlockSourceResult<'a, BlockHeaderData> {
Box::pin(async move {
let rpc = self.bitcoind_rpc_client.lock().await;
rpc.get_header(header_hash, height_hint).await
})
}
/// Gets a block given its hash.
fn get_block<'a>(&'a self, header_hash: &'a BlockHash) -> AsyncBlockSourceResult<'a, Block> {
Box::pin(async move {
let rpc = self.bitcoind_rpc_client.lock().await;
rpc.get_block(header_hash).await
})
}
/// Get the best block known by our node.
fn get_best_block(&self) -> AsyncBlockSourceResult<(BlockHash, Option<u32>)> {
Box::pin(async move {
let rpc = self.bitcoind_rpc_client.lock().await;
rpc.get_best_block().await
})
}
}
// TODO: This is not being used atm since we're using bitcoincore-rpc.
// Not deleting it since wd should need it once both get merged.
impl<'a> BitcoindClient<'a> {
/// Creates a new [BitcoindClient] instance.
pub async fn new(
host: &'a str,
port: u16,
rpc_user: &'a str,
rpc_password: &'a str,
teos_network: &'a str,
) -> std::io::Result<BitcoindClient<'a>> {
let http_endpoint = HttpEndpoint::for_host(host.to_owned()).with_port(port);
let rpc_credentials = base64::encode(&format!("{rpc_user}:{rpc_password}"));
let bitcoind_rpc_client = RpcClient::new(&rpc_credentials, http_endpoint)?;
let client = Self {
bitcoind_rpc_client: Arc::new(Mutex::new(bitcoind_rpc_client)),
host,
port,
rpc_user,
rpc_password,
};
// Test that bitcoind is reachable.
let btc_network = client.get_chain().await?;
// Assert teos runs on the same chain/network as bitcoind.
if btc_network != teos_network {
Err(Error::new(
ErrorKind::InvalidInput,
format!("bitcoind is running on {btc_network} but teosd is set to run on {teos_network}"),
))
} else {
Ok(client)
}
}
/// Gets a fresh RPC client.
pub fn get_new_rpc_client(&self) -> std::io::Result<RpcClient> {
let http_endpoint = HttpEndpoint::for_host(self.host.to_owned()).with_port(self.port);
let rpc_credentials = base64::encode(&format!("{}:{}", self.rpc_user, self.rpc_password));
RpcClient::new(&rpc_credentials, http_endpoint)
}
/// Gets the hash of the chain tip and its height.
pub async fn get_best_block_hash_and_height(
&self,
) -> Result<(BlockHash, Option<u32>), std::io::Error> {
let rpc = self.bitcoind_rpc_client.lock().await;
rpc.call_method::<(BlockHash, Option<u32>)>("getblockchaininfo", &[])
.await
}
/// Sends a transaction to the network.
pub async fn send_raw_transaction(&self, raw_tx: &Transaction) -> Result<Txid, std::io::Error> {
let rpc = self.bitcoind_rpc_client.lock().await;
let raw_tx_json = serde_json::json!(raw_tx.encode().to_hex());
rpc.call_method::<Txid>("sendrawtransaction", &[raw_tx_json])
.await
}
/// Gets a transaction given its id.
pub async fn get_raw_transaction(&self, txid: &Txid) -> Result<Transaction, std::io::Error> {
let rpc = self.bitcoind_rpc_client.lock().await;
let txid_hex = serde_json::json!(txid.encode().to_hex());
rpc.call_method::<Transaction>("getrawtransaction", &[txid_hex])
.await
}
/// Gets bitcoind's network.
pub async fn get_chain(&self) -> std::io::Result<String> {
// A wrapper type to extract "chain" key from getblockchaininfo JsonResponse.
struct BtcNetwork(String);
impl TryInto<BtcNetwork> for JsonResponse {
type Error = std::io::Error;
fn try_into(self) -> std::io::Result<BtcNetwork> {
Ok(BtcNetwork(self.0["chain"].as_str().unwrap().to_string()))
}
}
// Ask the RPC client for the network bitcoind is running on.
let rpc = self.bitcoind_rpc_client.lock().await;
let btc_network = rpc
.call_method::<BtcNetwork>("getblockchaininfo", &[])
.await?;
Ok(btc_network.0)
}
}