Skip to content
Closed
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
15 changes: 15 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,31 @@ jobs:
- toolchain: stable
build-net-tokio: true
build-no-std: true
build-examples: true
- toolchain: stable
platform: macos-latest
build-net-tokio: true
build-no-std: true
build-examples: true
- toolchain: beta
platform: macos-latest
build-net-tokio: true
build-no-std: true
build-examples: true
- toolchain: stable
platform: windows-latest
build-net-tokio: true
build-no-std: true
build-examples: true
- toolchain: beta
platform: windows-latest
build-net-tokio: true
build-no-std: true
build-examples: true
- toolchain: beta
build-net-tokio: true
build-no-std: true
build-examples: true
- toolchain: 1.36.0
build-no-std: false
test-log-variants: true
Expand Down Expand Up @@ -195,6 +201,15 @@ jobs:
# Maybe if codecov wasn't broken we wouldn't need to do this...
token: f421b687-4dc2-4387-ac3d-dc3b2528af57
fail_ci_if_error: true
- name: Build examples
if: matrix.build-examples
shell: bash
run: |
cd examples
for FILE in */; do
cd $FILE
cargo build --verbose --color always
done

benchmark:
runs-on: ubuntu-latest
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ lightning-c-bindings/a.out
Cargo.lock
.idea
lightning/target
/examples/Cargo.lock
/examples/target
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ members = [
"lightning-net-tokio",
"lightning-persister",
"lightning-background-processor",

"examples/*"
]

# Our tests do actual crypo and lots of work, the tradeoff for -O1 is well worth it.
Expand Down
25 changes: 25 additions & 0 deletions examples/bitcoind-rpc-client/Cargo.toml
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"
33 changes: 33 additions & 0 deletions examples/bitcoind-rpc-client/README.md
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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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.
This 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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
It implements some basic RPC methods that allow you to create a wallet and print it's balance to stdout.
It implements some basic RPC methods that allow you to create a wallet and print its balance to stdout.


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).
Copy link
Contributor

Choose a reason for hiding this comment

The 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).
To run 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).


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.



84 changes: 84 additions & 0 deletions examples/bitcoind-rpc-client/src/bitcoind_client.rs
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
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

}
73 changes: 73 additions & 0 deletions examples/bitcoind-rpc-client/src/convert.rs
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()))
}
}
53 changes: 53 additions & 0 deletions examples/bitcoind-rpc-client/src/main.rs
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);
}
6 changes: 3 additions & 3 deletions lightning-block-sync/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ impl RpcClient {
match e.get_ref().unwrap().downcast_ref::<HttpError>() {
Some(http_error) => match JsonResponse::try_from(http_error.contents.clone()) {
Ok(JsonResponse(response)) => response,
Err(_) => Err(e)?,
Err(_) => return Err(e),
},
None => Err(e)?,
None => return Err(e),
}
},
Err(e) => Err(e)?,
Err(e) => return Err(e),
};

if !response.is_object() {
Expand Down