Skip to content
Merged
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
7 changes: 5 additions & 2 deletions e2e-tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ use hex_conservative::DisplayHex;
use ldk_server_client::client::LdkServerClient;
use ldk_server_client::ldk_server_grpc::api::{GetNodeInfoRequest, GetNodeInfoResponse};
use ldk_server_grpc::api::{
GetBalancesRequest, ListChannelsRequest, OnchainReceiveRequest, OpenChannelRequest,
open_channel_request, GetBalancesRequest, ListChannelsRequest, OnchainReceiveRequest,
OpenChannelRequest,
};
use serde_json::Value;

Expand Down Expand Up @@ -737,7 +738,9 @@ pub async fn setup_funded_channel(
.open_channel(OpenChannelRequest {
node_pubkey: server_b.node_id().to_string(),
address: format!("127.0.0.1:{}", server_b.p2p_port),
channel_amount_sats,
amount: Some(open_channel_request::Amount::ChannelAmountSats(
channel_amount_sats,
)),
push_to_counterparty_msat: None,
channel_config: None,
announce_channel: true,
Expand Down
54 changes: 42 additions & 12 deletions e2e-tests/tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ use ldk_node::lightning::offers::offer::Offer;
use ldk_node::lightning_invoice::Bolt11Invoice;
use ldk_server_client::client::EventStream;
use ldk_server_client::ldk_server_grpc::api::{
Bolt11ReceiveRequest, Bolt12ReceiveRequest, GetBalancesRequest, OnchainReceiveRequest,
OpenChannelRequest,
open_channel_request, Bolt11ReceiveRequest, Bolt12ReceiveRequest, GetBalancesRequest,
OnchainReceiveRequest, OpenChannelRequest,
};
use ldk_server_client::ldk_server_grpc::events::event_envelope::Event;
use ldk_server_client::ldk_server_grpc::events::{
Expand Down Expand Up @@ -376,7 +376,7 @@ async fn test_cli_onchain_send_all() {
let balances_before = server.client().get_balances(GetBalancesRequest {}).await.unwrap();

let address = bitcoind.bitcoind.client.new_address().unwrap().to_string();
let output = run_cli(&server, &["onchain-send", &address, "--send-all", "true"]);
let output = run_cli(&server, &["onchain-send", &address, "all"]);
assert!(!output["txid"].as_str().unwrap().is_empty());

mine_and_sync(&bitcoind, &[&server], 6).await;
Expand Down Expand Up @@ -435,8 +435,7 @@ async fn test_cli_list_peers() {

// === CLI tests: Group 4 — Two-node with channel ===

#[tokio::test]
async fn test_cli_open_channel() {
async fn open_channel_via_cli(channel_amount: &str) {
let bitcoind = TestBitcoind::new();
let server_a = LdkServerHandle::start(&bitcoind).await;
let server_b = LdkServerHandle::start(&bitcoind).await;
Expand All @@ -454,11 +453,27 @@ async fn test_cli_open_channel() {
let addr = format!("127.0.0.1:{}", server_b.p2p_port);
let output = run_cli(
&server_a,
&["open-channel", server_b.node_id(), &addr, "100000sat", "--announce-channel"],
&[
"open-channel",
server_b.node_id(),
&addr,
channel_amount,
"--announce-channel",
],
);
assert!(!output["user_channel_id"].as_str().unwrap().is_empty());
}

#[tokio::test]
async fn test_cli_open_channel() {
open_channel_via_cli("100000sat").await;
}

#[tokio::test]
async fn test_cli_open_channel_with_all() {
open_channel_via_cli("all").await;
}

#[tokio::test]
async fn test_subscribe_events_channel_state_lifecycle_pending_ready_closed() {
let bitcoind = TestBitcoind::new();
Expand All @@ -481,7 +496,9 @@ async fn test_subscribe_events_channel_state_lifecycle_pending_ready_closed() {
.open_channel(OpenChannelRequest {
node_pubkey: server_b.node_id().to_string(),
address: format!("127.0.0.1:{}", server_b.p2p_port),
channel_amount_sats: 100_000,
amount: Some(open_channel_request::Amount::ChannelAmountSats(
100_000,
)),
push_to_counterparty_msat: None,
channel_config: None,
announce_channel: true,
Expand Down Expand Up @@ -645,7 +662,9 @@ async fn test_subscribe_events_channel_state_lifecycle_pending_ready_force_close
.open_channel(OpenChannelRequest {
node_pubkey: server_b.node_id().to_string(),
address: format!("127.0.0.1:{}", server_b.p2p_port),
channel_amount_sats: 100_000,
amount: Some(open_channel_request::Amount::ChannelAmountSats(
100_000,
)),
push_to_counterparty_msat: None,
channel_config: None,
announce_channel: true,
Expand Down Expand Up @@ -1119,18 +1138,29 @@ async fn test_cli_force_close_channel() {
assert!(channels_output["channels"].as_array().unwrap().is_empty());
}

#[tokio::test]
async fn test_cli_splice_in() {
async fn splice_in_via_cli(splice_amount: &str) {
let bitcoind = TestBitcoind::new();
let server_a = LdkServerHandle::start(&bitcoind).await;
let server_b = LdkServerHandle::start(&bitcoind).await;
let user_channel_id = setup_funded_channel(&bitcoind, &server_a, &server_b, 100_000).await;

let output =
run_cli(&server_a, &["splice-in", &user_channel_id, server_b.node_id(), "50000sat"]);
let output = run_cli(
&server_a,
&["splice-in", &user_channel_id, server_b.node_id(), splice_amount],
);
assert!(output.is_object());
}

#[tokio::test]
async fn test_cli_splice_in() {
splice_in_via_cli("50000sat").await;
}

#[tokio::test]
async fn test_cli_splice_in_with_all() {
splice_in_via_cli("all").await;
}

#[tokio::test]
async fn test_cli_splice_out() {
let bitcoind = TestBitcoind::new();
Expand Down
47 changes: 25 additions & 22 deletions ldk-server-cli/src/main.rs
Comment thread
benthecarman marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use ldk_server_client::error::LdkServerErrorCode::{
AuthError, InternalError, InternalServerError, InvalidRequestError, LightningError,
};
use ldk_server_client::ldk_server_grpc::api::{
onchain_send_request, open_channel_request, splice_in_request, AllFunds,
Bolt11ClaimForHashRequest, Bolt11ClaimForHashResponse, Bolt11FailForHashRequest,
Bolt11FailForHashResponse, Bolt11ReceiveForHashRequest, Bolt11ReceiveForHashResponse,
Bolt11ReceiveRequest, Bolt11ReceiveResponse, Bolt11ReceiveVariableAmountViaJitChannelRequest,
Expand Down Expand Up @@ -56,8 +57,8 @@ use ldk_server_client::{
use serde::Serialize;
use serde_json::{json, Value};
use types::{
Amount, CliListForwardedPaymentsResponse, CliListPaymentsResponse, CliPaginatedResponse,
Preimage,
Amount, AmountOrAll, CliListForwardedPaymentsResponse, CliListPaymentsResponse,
CliPaginatedResponse, Preimage,
};

mod types;
Expand Down Expand Up @@ -115,14 +116,9 @@ enum Commands {
#[arg(help = "The address to send coins to")]
address: String,
#[arg(
help = "The amount to send, e.g. 50sat or 50000msat, must be a whole sat amount, cannot send msats on-chain. Will respect any on-chain reserve needed for anchor channels"
help = "The amount to send, e.g. 50sat or 50000msat, or 'all' to use all available on-chain funds. Exact amounts must be a whole sat amount. Will respect any on-chain reserve needed for anchor channels"
)]
amount: Option<Amount>,
#[arg(
long,
help = "Send all available balance to the address while retaining on-chain reserves for anchor channels"
)]
send_all: Option<bool>,
amount: AmountOrAll,
#[arg(
long,
help = "Fee rate in satoshis per virtual byte. If not set, a reasonable estimate will be used"
Expand Down Expand Up @@ -419,9 +415,9 @@ enum Commands {
)]
address: String,
#[arg(
help = "The amount to commit to the channel, e.g. 100sat or 100000msat, must be a whole sat amount, cannot send msats on-chain."
help = "The amount to commit to the channel, e.g. 100sat or 100000msat, or 'all' to use all available on-chain funds. Exact amounts must be a whole sat amount."
)]
channel_amount: Amount,
channel_amount: AmountOrAll,
#[arg(long, help = "Amount to push to the remote side, e.g. 50sat or 50000msat")]
push_to_counterparty: Option<Amount>,
#[arg(long, help = "Whether the channel should be public")]
Expand Down Expand Up @@ -457,9 +453,9 @@ enum Commands {
#[arg(help = "The hex-encoded public key of the channel's counterparty node")]
counterparty_node_id: String,
#[arg(
help = "The amount to splice into the channel, e.g. 50sat or 50000msat, must be a whole sat amount, cannot send msats on-chain."
help = "The amount to splice into the channel, e.g. 50sat or 50000msat, or 'all' to use all available on-chain funds. Exact amounts must be a whole sat amount."
)]
splice_amount: Amount,
splice_amount: AmountOrAll,
},
#[command(about = "Decrease the channel balance by the given amount")]
SpliceOut {
Expand Down Expand Up @@ -660,15 +656,17 @@ async fn main() {
client.onchain_receive(OnchainReceiveRequest {}).await,
);
},
Commands::OnchainSend { address, amount, send_all, fee_rate_sat_per_vb } => {
let amount_sats = amount.map(|a| a.to_sat().unwrap_or_else(|e| handle_error_msg(e)));
Commands::OnchainSend { address, amount, fee_rate_sat_per_vb } => {
let amount = match amount.to_sat().unwrap_or_else(|e| handle_error_msg(e)) {
Some(amount_sats) => onchain_send_request::Amount::AmountSats(amount_sats),
None => onchain_send_request::Amount::AllFunds(AllFunds {}),
};
handle_response_result::<_, OnchainSendResponse>(
client
.onchain_send(OnchainSendRequest {
address,
amount_sats,
send_all,
fee_rate_sat_per_vb,
amount: Some(amount),
})
.await,
);
Expand Down Expand Up @@ -983,8 +981,10 @@ async fn main() {
forwarding_fee_base_msat,
cltv_expiry_delta,
} => {
let channel_amount_sats =
channel_amount.to_sat().unwrap_or_else(|e| handle_error_msg(e));
let amount = match channel_amount.to_sat().unwrap_or_else(|e| handle_error_msg(e)) {
Some(amount_sats) => open_channel_request::Amount::ChannelAmountSats(amount_sats),
None => open_channel_request::Amount::AllFunds(AllFunds {}),
};
let push_to_counterparty_msat = push_to_counterparty.map(|a| a.to_msat());
let channel_config = build_open_channel_config(
forwarding_fee_proportional_millionths,
Expand All @@ -1003,7 +1003,7 @@ async fn main() {
.open_channel(OpenChannelRequest {
node_pubkey,
address,
channel_amount_sats,
amount: Some(amount),
push_to_counterparty_msat,
channel_config,
announce_channel,
Expand All @@ -1013,13 +1013,16 @@ async fn main() {
);
},
Commands::SpliceIn { user_channel_id, counterparty_node_id, splice_amount } => {
let splice_amount_sats = splice_amount.to_sat().unwrap_or_else(|e| handle_error_msg(e));
let amount = match splice_amount.to_sat().unwrap_or_else(|e| handle_error_msg(e)) {
Some(amount_sats) => splice_in_request::Amount::SpliceAmountSats(amount_sats),
None => splice_in_request::Amount::AllFunds(AllFunds {}),
};
handle_response_result::<_, SpliceInResponse>(
client
.splice_in(SpliceInRequest {
user_channel_id,
counterparty_node_id,
splice_amount_sats,
amount: Some(amount),
})
.await,
);
Expand Down
37 changes: 37 additions & 0 deletions ldk-server-cli/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,35 @@ impl FromStr for Amount {
}
}

/// An exact on-chain amount or all available on-chain funds.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AmountOrAll {
Exact(Amount),
All,
}

impl AmountOrAll {
/// Returns the exact amount in satoshis, or `None` when all funds should be used.
pub fn to_sat(self) -> Result<Option<u64>, String> {
match self {
Self::Exact(amount) => amount.to_sat().map(Some),
Self::All => Ok(None),
}
}
}

impl FromStr for AmountOrAll {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.trim() == "all" {
Ok(Self::All)
} else {
Amount::from_str(s).map(Self::Exact)
}
}
}

/// A validated 32-byte payment preimage, parsed from a 64-character hex string.
#[derive(Debug, Clone)]
pub struct Preimage(pub [u8; 32]);
Expand Down Expand Up @@ -207,6 +236,14 @@ mod tests {
assert!(Amount::from_str(&big).is_err());
}

#[test]
fn amount_or_all_parses_exact_amount_or_all() {
assert_eq!(AmountOrAll::from_str("all").unwrap(), AmountOrAll::All);
assert_eq!(AmountOrAll::from_str(" all ").unwrap(), AmountOrAll::All);
assert_eq!(AmountOrAll::from_str("100sat").unwrap().to_sat().unwrap(), Some(100));
assert_eq!(AmountOrAll::All.to_sat().unwrap(), None);
}

#[test]
fn preimage_parsing_and_roundtrip() {
// valid 64-char hex string
Expand Down
Loading