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
2 changes: 1 addition & 1 deletion e2e-tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -985,7 +985,7 @@ pub async fn setup_funded_channel(
.client()
.open_channel(OpenChannelRequest {
node_pubkey: server_b.node_id().to_string(),
address: format!("127.0.0.1:{}", server_b.p2p_port),
address: Some(format!("127.0.0.1:{}", server_b.p2p_port)),
amount: Some(open_channel_request::Amount::ChannelAmountSats(channel_amount_sats)),
push_to_counterparty_msat: None,
channel_config: None,
Expand Down
34 changes: 28 additions & 6 deletions e2e-tests/tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -675,7 +675,9 @@ async fn test_cli_list_peers() {

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

async fn open_channel_via_cli(channel_amount: &str) {
async fn open_channel_via_cli(
channel_amount: &str,
) -> (TestBitcoind, LdkServerHandle, LdkServerHandle, String) {
let bitcoind = TestBitcoind::new();
let server_a = LdkServerHandle::start(&bitcoind).await;
let server_b = LdkServerHandle::start(&bitcoind).await;
Expand All @@ -693,14 +695,34 @@ async fn open_channel_via_cli(channel_amount: &str) {
let addr = format!("127.0.0.1:{}", server_b.p2p_port);
let output = run_cli(
&server_a,
&["open-channel", server_b.node_id(), &addr, channel_amount, "--announce-channel"],
&[
"open-channel",
server_b.node_id(),
channel_amount,
"--address",
&addr,
"--announce-channel",
],
);
assert!(!output["user_channel_id"].as_str().unwrap().is_empty());
let user_channel_id = output["user_channel_id"].as_str().unwrap().to_string();
assert!(!user_channel_id.is_empty());
(bitcoind, server_a, server_b, user_channel_id)
}

#[tokio::test]
async fn test_cli_open_channel() {
open_channel_via_cli("100000sat").await;
let (bitcoind, server_a, server_b, first_user_channel_id) =
open_channel_via_cli("100000sat").await;

// Confirm first channel is usable before opening a second one.
mine_and_sync(&bitcoind, &[&server_a, &server_b], 6).await;
wait_for_usable_channel(server_a.client(), &bitcoind, Duration::from_secs(60)).await;

// Open a second channel without specifying address (resolved by backend from connected peers).
let output = run_cli(&server_a, &["open-channel", server_b.node_id(), "50000sat"]);
let second_user_channel_id = output["user_channel_id"].as_str().unwrap().to_string();
assert!(!second_user_channel_id.is_empty());
assert_ne!(first_user_channel_id, second_user_channel_id);
}

#[tokio::test]
Expand Down Expand Up @@ -729,7 +751,7 @@ async fn test_subscribe_events_channel_state_lifecycle_pending_ready_closed() {
.client()
.open_channel(OpenChannelRequest {
node_pubkey: server_b.node_id().to_string(),
address: format!("127.0.0.1:{}", server_b.p2p_port),
address: Some(format!("127.0.0.1:{}", server_b.p2p_port)),
amount: Some(open_channel_request::Amount::ChannelAmountSats(100_000)),
push_to_counterparty_msat: None,
channel_config: None,
Expand Down Expand Up @@ -901,7 +923,7 @@ async fn test_subscribe_events_channel_state_lifecycle_pending_ready_force_close
.client()
.open_channel(OpenChannelRequest {
node_pubkey: server_b.node_id().to_string(),
address: format!("127.0.0.1:{}", server_b.p2p_port),
address: Some(format!("127.0.0.1:{}", server_b.p2p_port)),
amount: Some(open_channel_request::Amount::ChannelAmountSats(100_000)),
push_to_counterparty_msat: None,
channel_config: None,
Expand Down
2 changes: 1 addition & 1 deletion e2e-tests/tests/postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ async fn test_postgres_persistence_and_sqlite_interoperability() {
.client()
.open_channel(OpenChannelRequest {
node_pubkey: sqlite_c.node_id().to_string(),
address: format!("127.0.0.1:{}", sqlite_c.p2p_port),
address: Some(format!("127.0.0.1:{}", sqlite_c.p2p_port)),
amount: Some(open_channel_request::Amount::ChannelAmountSats(1_000_000)),
push_to_counterparty_msat: Some(50_000_000),
channel_config: None,
Expand Down
37 changes: 27 additions & 10 deletions ldk-server-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -567,16 +567,18 @@ enum Commands {
},
#[command(about = "Create a new outbound channel to the given remote node")]
OpenChannel {
#[arg(help = "The hex-encoded public key of the node to open a channel with")]
#[arg(help = "The node to open a channel with, as hex pubkey or pubkey@address format")]
node_pubkey: String,
#[arg(
help = "Address to connect to remote peer (IPv4:port, IPv6:port, OnionV3:port, or hostname:port)"
)]
address: String,
#[arg(
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: AmountOrAll,
#[arg(
short,
long,
help = "Address to connect to remote peer (IPv4:port, IPv6:port, OnionV3:port, or hostname:port). Optional if included in pubkey via @ separator or if peer is already connected."
)]
address: Option<String>,
#[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 @@ -1344,15 +1346,23 @@ async fn main() {
},
Commands::OpenChannel {
node_pubkey,
address,
channel_amount,
address,
push_to_counterparty,
announce_channel,
disable_counterparty_reserve,
forwarding_fee_proportional_millionths,
forwarding_fee_base_msat,
cltv_expiry_delta,
} => {
let (node_pubkey, inline_address) = parse_node_pubkey_and_inline_address(node_pubkey);
if inline_address.is_some() && address.is_some() {
handle_error_msg(
"Address was provided twice. Use either pubkey@address or a separate address argument."
.to_string(),
);
}
let address = address.or(inline_address);
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 {}),
Expand Down Expand Up @@ -1548,10 +1558,9 @@ async fn main() {
);
},
Commands::ConnectPeer { node_pubkey, address, persist } => {
let (node_pubkey, address) = if let Some(address) = address {
(node_pubkey, address)
} else if let Some((pubkey, addr)) = node_pubkey.split_once('@') {
(pubkey.to_string(), addr.to_string())
let (node_pubkey, inline_address) = parse_node_pubkey_and_inline_address(node_pubkey);
let address = if let Some(address) = address.or(inline_address) {
address
} else {
eprintln!("Error: address is required. Provide it as pubkey@address or as a separate argument.");
std::process::exit(1);
Expand Down Expand Up @@ -1655,6 +1664,14 @@ fn load_client_config(explicit_path: Option<PathBuf>) -> Result<Option<Config>,
}
}

fn parse_node_pubkey_and_inline_address(node_pubkey: String) -> (String, Option<String>) {
if let Some((pubkey, address)) = node_pubkey.split_once('@') {
(pubkey.to_string(), Some(address.to_string()))
} else {
(node_pubkey, None)
}
}

fn build_open_channel_config(
forwarding_fee_proportional_millionths: Option<u32>, forwarding_fee_base_msat: Option<u32>,
cltv_expiry_delta: Option<u32>,
Expand Down
9 changes: 5 additions & 4 deletions ldk-server-grpc/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -768,10 +768,11 @@ pub struct OpenChannelRequest {
/// The hex-encoded public key of the node to open a channel with.
#[prost(string, tag = "1")]
pub node_pubkey: ::prost::alloc::string::String,
/// An address which can be used to connect to a remote peer.
/// It can be of type IPv4:port, IPv6:port, OnionV3:port or hostname:port
#[prost(string, tag = "2")]
pub address: ::prost::alloc::string::String,
/// An optional address which can be used to connect to a remote peer.
/// It can be of type IPv4:port, IPv6:port, OnionV3:port or hostname:port.
/// If unset, the server will try to resolve it from currently connected peers.
#[prost(string, optional, tag = "2")]
pub address: ::core::option::Option<::prost::alloc::string::String>,
/// The amount of satoshis to push to the remote side as part of the initial commitment state.
#[prost(uint64, optional, tag = "4")]
pub push_to_counterparty_msat: ::core::option::Option<u64>,
Expand Down
7 changes: 4 additions & 3 deletions ldk-server-grpc/src/proto/api.proto
Original file line number Diff line number Diff line change
Expand Up @@ -580,9 +580,10 @@ message OpenChannelRequest {
// The hex-encoded public key of the node to open a channel with.
string node_pubkey = 1;

// An address which can be used to connect to a remote peer.
// It can be of type IPv4:port, IPv6:port, OnionV3:port or hostname:port
string address = 2;
// An optional address which can be used to connect to a remote peer.
// It can be of type IPv4:port, IPv6:port, OnionV3:port or hostname:port.
// If unset, the server will try to resolve it from currently connected peers.
optional string address = 2;

// Required. The funds to commit to the channel.
oneof amount {
Expand Down
4 changes: 2 additions & 2 deletions ldk-server-mcp/src/tools/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,7 @@ pub fn open_channel_schema() -> Value {
},
"address": {
"type": "string",
"description": "Address of the remote peer (IPv4:port, IPv6:port, OnionV3:port, or hostname:port)"
"description": "Address of the remote peer (IPv4:port, IPv6:port, OnionV3:port, or hostname:port). If omitted, the address of the currently connected peer is used"
},
"channel_amount_sats": {
"oneOf": [
Expand All @@ -634,7 +634,7 @@ pub fn open_channel_schema() -> Value {
},
"channel_config": channel_config_schema()
},
"required": ["node_pubkey", "address", "channel_amount_sats"]
"required": ["node_pubkey", "channel_amount_sats"]
})
}

Expand Down
19 changes: 17 additions & 2 deletions ldk-server/src/api/open_channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,23 @@ pub(crate) async fn handle_open_channel(
) -> Result<OpenChannelResponse, LdkServerError> {
let node_id = PublicKey::from_str(&request.node_pubkey)
.map_err(|_| ldk_node::NodeError::InvalidPublicKey)?;
let address = SocketAddress::from_str(&request.address)
.map_err(|_| ldk_node::NodeError::InvalidSocketAddress)?;
let address = match request.address {
Some(address) => {
SocketAddress::from_str(&address).map_err(|_| ldk_node::NodeError::InvalidSocketAddress)?
},
None => context
.node
.list_peers()
.into_iter()
.find(|peer| peer.node_id == node_id)
.map(|peer| peer.address)
.ok_or_else(|| {
LdkServerError::new(
LdkServerErrorCode::InvalidRequestError,
"Address is required unless the peer is currently connected. Provide an address or connect-peer first.",
)
})?,
};

let amount = require_amount(request.amount)?;

Expand Down
Loading