-
Notifications
You must be signed in to change notification settings - Fork 421
Adds bitcoin-rpc-client example #1232
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,3 +8,5 @@ lightning-c-bindings/a.out | |
| Cargo.lock | ||
| .idea | ||
| lightning/target | ||
| /examples/Cargo.lock | ||
| /examples/target | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| [package] | ||
| name = "bitcoind-rpc-client" | ||
| version = "0.1.0" | ||
| authors = ["Conor Okus <conor@squarecrypto.org>"] | ||
| edition = "2018" | ||
| license = "MIT OR Apache-2.0" | ||
|
|
||
| [dependencies] | ||
| lightning = { path = "../../lightning" } | ||
| lightning-block-sync = { path = "../../lightning-block-sync", features = ["rpc-client"]} | ||
| lightning-net-tokio = { path = "../../lightning-net-tokio" } | ||
|
|
||
| base64 = "0.13.0" | ||
| bitcoin = "0.27" | ||
| bitcoin-bech32 = "0.12" | ||
| bech32 = "0.8" | ||
| hex = "0.3" | ||
| serde_json = { version = "1.0" } | ||
| tokio = { version = "1.5", features = [ "io-util", "macros", "rt", "rt-multi-thread", "sync", "net", "time" ] } | ||
|
|
||
| [profile.release] | ||
| panic = "abort" | ||
|
|
||
| [profile.dev] | ||
| panic = "abort" |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,33 @@ | ||||||
| # Wallet actions with an RPC client | ||||||
|
|
||||||
| This is example shows how you could create a client that directly communicates with bitcoind from LDK. The API is flexible and allows for different ways to implement the interface. | ||||||
|
|
||||||
| It implements some basic RPC methods that allow you to create a wallet and print it's balance to stdout. | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| To run with this example you need to have a bitcoin core node running in regtest mode. Get the bitcoin core binary either from the [bitcoin core repo](https://bitcoincore.org/bin/bitcoin-core-0.22.0/) or [build from source](https://github.com/bitcoin/bitcoin/blob/v0.21.1/doc/build-unix.md). | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| Then configure the node with the following `bitcoin.conf` | ||||||
|
|
||||||
| ``` | ||||||
| regtest=1 | ||||||
| fallbackfee=0.0001 | ||||||
| server=1 | ||||||
| txindex=1 | ||||||
| rpcuser=admin | ||||||
| rpcpassword=password | ||||||
| ``` | ||||||
|
|
||||||
| ## How to use | ||||||
|
|
||||||
| ``` | ||||||
| Cargo run | ||||||
| ``` | ||||||
|
|
||||||
| ## Notes | ||||||
|
|
||||||
| `RpcClient` is a simple RPC client for calling methods using HTTP POST. It is implemented in [rust-lightning/lightning-block-sync/rpc.rs](https://github.com/lightningdevkit/rust-lightning/blob/61341df39e90de9d650851a624c0644f5c9dd055/lightning-block-sync/src/rpc.rs) | ||||||
|
|
||||||
| The purpose of `RpcClient` is to create a new RPC client connected to the given endpoint with the provided credentials. The credentials should be a base64 encoding of a user name and password joined by a colon, as is required for HTTP basic access authentication. | ||||||
|
|
||||||
|
|
||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| use std::{sync::{Arc}, io}; | ||
|
|
||
| use lightning_block_sync::{rpc::RpcClient, http::{HttpEndpoint}}; | ||
| use serde_json::json; | ||
| use tokio::sync::Mutex; | ||
|
|
||
| use crate::convert::{CreateWalletResponse, BlockchainInfoResponse, NewAddressResponse, GetBalanceResponse, GenerateToAddressResponse}; | ||
|
|
||
|
|
||
| pub struct BitcoindClient { | ||
| bitcoind_rpc_client: Arc<Mutex<RpcClient>>, | ||
| host: String, | ||
| port: u16, | ||
| rpc_user: String, | ||
| rpc_password: String, | ||
| } | ||
|
|
||
| impl BitcoindClient { | ||
| pub async fn new(host: String, port: u16, rpc_user: String, rpc_password: String) -> io::Result<Self> { | ||
| let http_endpoint = HttpEndpoint::for_host(host.clone()).with_port(port); | ||
| let rpc_creditials = | ||
| base64::encode(format!("{}:{}", rpc_user.clone(), rpc_password.clone())); | ||
| let mut bitcoind_rpc_client = RpcClient::new(&rpc_creditials, http_endpoint)?; | ||
| bitcoind_rpc_client | ||
| .call_method::<BlockchainInfoResponse>("getblockchaininfo", &vec![]) | ||
| .await | ||
| .map_err(|_| { | ||
| io::Error::new(io::ErrorKind::PermissionDenied, | ||
| "Failed to make initial call to bitcoind - please check your RPC user/password and access settings") | ||
| })?; | ||
|
|
||
| let client = Self { | ||
| bitcoind_rpc_client: Arc::new(Mutex::new(bitcoind_rpc_client)), | ||
| host, | ||
| port, | ||
| rpc_user, | ||
| rpc_password, | ||
| }; | ||
|
|
||
| Ok(client) | ||
| } | ||
|
|
||
| pub fn get_new_rpc_client(&self) -> io::Result<RpcClient> { | ||
| let http_endpoint = HttpEndpoint::for_host(self.host.clone()).with_port(self.port); | ||
| let rpc_credentials = | ||
| base64::encode(format!("{}:{}", self.rpc_user.clone(), self.rpc_password.clone())); | ||
| RpcClient::new(&rpc_credentials, http_endpoint) | ||
| } | ||
|
|
||
| pub async fn get_blockchain_info(&self) -> BlockchainInfoResponse { | ||
| let mut rpc = self.bitcoind_rpc_client.lock().await; | ||
| rpc.call_method::<BlockchainInfoResponse>("getblockchaininfo", &vec![]).await.unwrap() | ||
| } | ||
|
|
||
| pub async fn create_wallet(&self) -> CreateWalletResponse { | ||
| let mut rpc = self.bitcoind_rpc_client.lock().await; | ||
| let create_wallet_args = vec![json!("test-wallet")]; | ||
|
|
||
| rpc.call_method::<CreateWalletResponse>("createwallet", &create_wallet_args).await.unwrap() | ||
| } | ||
|
|
||
| pub async fn get_new_address(&self) -> String { | ||
| let mut rpc = self.bitcoind_rpc_client.lock().await; | ||
|
|
||
| let addr_args = vec![json!("LDK output address")]; | ||
| let addr = rpc.call_method::<NewAddressResponse>("getnewaddress", &addr_args).await.unwrap(); | ||
| addr.0.to_string() | ||
| } | ||
|
|
||
| pub async fn get_balance(&self) -> GetBalanceResponse { | ||
| let mut rpc = self.bitcoind_rpc_client.lock().await; | ||
|
|
||
| rpc.call_method::<GetBalanceResponse>("getbalance", &vec![]).await.unwrap() | ||
| } | ||
|
|
||
| pub async fn generate_to_address(&self, block_num: u64, address: &str) -> GenerateToAddressResponse { | ||
| let mut rpc = self.bitcoind_rpc_client.lock().await; | ||
|
|
||
| let generate_to_address_args = vec![json!(block_num), json!(address)]; | ||
|
|
||
|
|
||
| rpc.call_method::<GenerateToAddressResponse>("generatetoaddress", &generate_to_address_args).await.unwrap() | ||
| } | ||
|
Comment on lines
+43
to
+83
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Indentation of function bodies is a bit off in some places here. |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| use std::convert::TryInto; | ||
|
|
||
| use bitcoin::{Amount, BlockHash, hashes::hex::FromHex}; | ||
| use lightning_block_sync::http::JsonResponse; | ||
|
|
||
| /// TryInto implementation specifies the conversion logic from json response to BlockchainInfo object. | ||
| pub struct BlockchainInfoResponse { | ||
| pub latest_height: usize, | ||
| pub latest_blockhash: BlockHash, | ||
| pub chain: String, | ||
| } | ||
|
|
||
| impl TryInto<BlockchainInfoResponse> for JsonResponse { | ||
| type Error = std::io::Error; | ||
| fn try_into(self) -> std::io::Result<BlockchainInfoResponse> { | ||
| Ok(BlockchainInfoResponse { | ||
| latest_height: self.0["blocks"].as_u64().unwrap() as usize, | ||
| latest_blockhash: BlockHash::from_hex(self.0["bestblockhash"].as_str().unwrap()) | ||
| .unwrap(), | ||
| chain: self.0["chain"].as_str().unwrap().to_string(), | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| pub struct CreateWalletResponse { | ||
| pub name: String, | ||
| pub warning: String, | ||
| } | ||
|
|
||
| impl TryInto<CreateWalletResponse> for JsonResponse { | ||
| type Error = std::io::Error; | ||
| fn try_into(self) -> std::io::Result<CreateWalletResponse> { | ||
| Ok(CreateWalletResponse { | ||
| name: self.0["name"].as_str().unwrap().to_string(), | ||
| warning: self.0["warning"].as_str().unwrap().to_string(), | ||
| }) | ||
| } | ||
| } | ||
| pub struct GetBalanceResponse(pub Amount); | ||
|
|
||
| impl TryInto<GetBalanceResponse> for JsonResponse { | ||
| type Error = std::io::Error; | ||
| fn try_into(self) -> std::io::Result<GetBalanceResponse> { | ||
| let balance = Amount::from_btc(self.0.as_f64().unwrap()).unwrap(); | ||
| Ok(GetBalanceResponse(balance)) | ||
| } | ||
| } | ||
|
|
||
| pub struct GenerateToAddressResponse(pub Vec<BlockHash>); | ||
|
|
||
| impl TryInto<GenerateToAddressResponse> for JsonResponse { | ||
| type Error = std::io::Error; | ||
| fn try_into(self) -> std::io::Result<GenerateToAddressResponse> { | ||
| let mut x: Vec<BlockHash> = Vec::new(); | ||
|
|
||
| for item in self.0.as_array().unwrap() { | ||
| x.push(BlockHash::from_hex(item.as_str().unwrap()) | ||
| .unwrap()); | ||
| } | ||
|
|
||
| Ok(GenerateToAddressResponse(x)) | ||
| } | ||
| } | ||
|
|
||
|
|
||
| pub struct NewAddressResponse(pub String); | ||
|
|
||
| impl TryInto<NewAddressResponse> for JsonResponse { | ||
| type Error = std::io::Error; | ||
| fn try_into(self) -> std::io::Result<NewAddressResponse> { | ||
| Ok(NewAddressResponse(self.0.as_str().unwrap().to_string())) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| pub mod bitcoind_client; | ||
|
|
||
| use std::{sync::Arc}; | ||
|
|
||
| use crate::bitcoind_client::BitcoindClient; | ||
|
|
||
| mod convert; | ||
|
|
||
| #[tokio::main] | ||
| pub async fn main() { | ||
| start_ldk().await; | ||
| } | ||
|
|
||
| async fn start_ldk() { | ||
| // Initialize our bitcoind client | ||
| let bitcoind_client = match BitcoindClient::new( | ||
| String::from("127.0.0.1"), | ||
| 18443, | ||
| String::from("admin"), | ||
| String::from("password") | ||
| ) | ||
| .await | ||
| { | ||
| Ok(client) => { | ||
| println!("Successfully connected to bitcoind client"); | ||
| Arc::new(client) | ||
| }, | ||
| Err(e) => { | ||
| println!("Failed to connect to bitcoind client: {}", e); | ||
| return; | ||
| } | ||
| }; | ||
|
|
||
| // Check we connected to the expected network | ||
| let bitcoind_blockchain_info = bitcoind_client.get_blockchain_info().await; | ||
| println!("Chain network: {}", bitcoind_blockchain_info.chain); | ||
| println!("Latest block height: {}", bitcoind_blockchain_info.latest_height); | ||
|
|
||
| // Create a named bitcoin core wallet | ||
| let bitcoind_wallet = bitcoind_client.create_wallet().await; | ||
| println!("Successfully created wallet with name: {}", bitcoind_wallet.name); | ||
|
|
||
| // Generate a new address | ||
| let bitcoind_new_address = bitcoind_client.get_new_address().await; | ||
| println!("Address: {}", bitcoind_new_address); | ||
|
|
||
| // Generate 101 blocks and use the above address as coinbase | ||
| bitcoind_client.generate_to_address(101, &bitcoind_new_address).await; | ||
|
|
||
| // Show balance | ||
| let balance = bitcoind_client.get_balance().await; | ||
| println!("Balance: {}", balance.0); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.