Skip to content

Commit ae879f3

Browse files
committed
Add support for sending to human-readable names that resolve to Bolt12 Offers
BIP 353 introduced human-readable names which has been implemented in LDK (0.1). This commit adds support for sending to HRNs that resolve to Bolt12 Offers by using the LDK pay_for_offer_from_human_readable_name method in ChannelManager.
1 parent 01c396a commit ae879f3

File tree

10 files changed

+249
-19
lines changed

10 files changed

+249
-19
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ panic = 'abort' # Abort on panic
2828
default = []
2929

3030
[dependencies]
31-
lightning = { version = "0.1.0", features = ["std"] }
31+
lightning = { version = "0.1.0", features = ["std", "dnssec"] }
3232
lightning-types = { version = "0.2.0" }
3333
lightning-invoice = { version = "0.33.0", features = ["std"] }
3434
lightning-net-tokio = { version = "0.1.0" }

bindings/ldk_node.udl

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ interface Bolt12Payment {
197197
[Throws=NodeError]
198198
PaymentId send_using_amount([ByRef]Offer offer, u64 amount_msat, u64? quantity, string? payer_note);
199199
[Throws=NodeError]
200+
PaymentId send_to_human_readable_name([ByRef]HumanReadableName hrn, u64 amount_msat);
201+
[Throws=NodeError]
200202
Offer receive(u64 amount_msat, [ByRef]string description, u32? expiry_secs, u64? quantity);
201203
[Throws=NodeError]
202204
Offer receive_variable_amount([ByRef]string description, u32? expiry_secs);
@@ -248,6 +250,13 @@ interface LSPS1Liquidity {
248250
LSPS1OrderStatus check_order_status(OrderId order_id);
249251
};
250252

253+
interface HumanReadableName {
254+
[Throws=NodeError, Name=from_encoded]
255+
constructor([ByRef] string encoded);
256+
string user();
257+
string domain();
258+
};
259+
251260
[Error]
252261
enum NodeError {
253262
"AlreadyRunning",
@@ -302,6 +311,7 @@ enum NodeError {
302311
"InsufficientFunds",
303312
"LiquiditySourceUnavailable",
304313
"LiquidityFeeTooHigh",
314+
"HrnParsingFailed",
305315
};
306316

307317
dictionary NodeStatus {
@@ -402,7 +412,7 @@ interface PaymentKind {
402412
Onchain(Txid txid, ConfirmationStatus status);
403413
Bolt11(PaymentHash hash, PaymentPreimage? preimage, PaymentSecret? secret);
404414
Bolt11Jit(PaymentHash hash, PaymentPreimage? preimage, PaymentSecret? secret, u64? counterparty_skimmed_fee_msat, LSPFeeLimits lsp_fee_limits);
405-
Bolt12Offer(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret, OfferId offer_id, UntrustedString? payer_note, u64? quantity);
415+
Bolt12Offer(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret, OfferId? offer_id, UntrustedString? payer_note, u64? quantity);
406416
Bolt12Refund(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret, UntrustedString? payer_note, u64? quantity);
407417
Spontaneous(PaymentHash hash, PaymentPreimage? preimage);
408418
};

src/config.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,19 @@ impl Default for Config {
185185
}
186186
}
187187

188+
/// Configuration options for Human-Readable Names ([BIP 353]).
189+
///
190+
/// [BIP 353]: https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki
191+
#[derive(Debug, Clone)]
192+
pub struct HumanReadableNamesConfig {
193+
/// The DNS resolvers to be used for resolving Human-Readable Names.
194+
///
195+
/// If not empty, the values set will be used as DNS resolvers when sending to HRNs.
196+
///
197+
/// **Note:** If empty, payments to HRNs will fail.
198+
pub dns_resolvers_node_ids: Vec<PublicKey>,
199+
}
200+
188201
/// Configuration options pertaining to 'Anchor' channels, i.e., channels for which the
189202
/// `option_anchors_zero_fee_htlc_tx` channel type is negotiated.
190203
///
@@ -306,6 +319,7 @@ pub(crate) fn default_user_config(config: &Config) -> UserConfig {
306319
let mut user_config = UserConfig::default();
307320
user_config.channel_handshake_limits.force_announced_channel_preference = false;
308321
user_config.manually_accept_inbound_channels = true;
322+
user_config.manually_handle_bolt12_invoices = true;
309323
user_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx =
310324
config.anchor_channels_config.is_some();
311325

src/error.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ pub enum Error {
120120
LiquiditySourceUnavailable,
121121
/// The given operation failed due to the LSP's required opening fee being too high.
122122
LiquidityFeeTooHigh,
123+
/// Parsing a Human-Readable Name has failed.
124+
HrnParsingFailed,
125+
/// The given operation failed due to DNS resolvers not being configured.
126+
DnsResolversUnavailable,
123127
}
124128

125129
impl fmt::Display for Error {
@@ -193,6 +197,12 @@ impl fmt::Display for Error {
193197
Self::LiquidityFeeTooHigh => {
194198
write!(f, "The given operation failed due to the LSP's required opening fee being too high.")
195199
},
200+
Self::HrnParsingFailed => {
201+
write!(f, "Failed to parse a human-readable name.")
202+
},
203+
Self::DnsResolversUnavailable => {
204+
write!(f, "The given operation failed due to DNS resolvers not being configured.")
205+
},
196206
}
197207
}
198208
}

src/event.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -742,7 +742,7 @@ where
742742
hash: Some(payment_hash),
743743
preimage: payment_preimage,
744744
secret: Some(payment_secret),
745-
offer_id,
745+
offer_id: Some(offer_id),
746746
payer_note,
747747
quantity,
748748
};
@@ -1417,8 +1417,29 @@ where
14171417
);
14181418
}
14191419
},
1420-
LdkEvent::InvoiceReceived { .. } => {
1421-
debug_assert!(false, "We currently don't handle BOLT12 invoices manually, so this event should never be emitted.");
1420+
LdkEvent::InvoiceReceived { payment_id, invoice, context, responder: _ } => {
1421+
let update = PaymentDetailsUpdate {
1422+
hash: Some(Some(invoice.payment_hash())),
1423+
quantity: invoice.quantity(),
1424+
..PaymentDetailsUpdate::new(payment_id)
1425+
};
1426+
1427+
match self.payment_store.update(&update) {
1428+
Ok(_) => {},
1429+
Err(e) => {
1430+
log_error!(self.logger, "Failed to access payment store: {}", e);
1431+
return Err(ReplayEvent());
1432+
},
1433+
};
1434+
1435+
match self
1436+
.channel_manager
1437+
.send_payment_for_bolt12_invoice(&invoice, context.as_ref()) {
1438+
Ok(_) => {},
1439+
Err(e) => {
1440+
log_error!(self.logger, "Error while paying invoice: {:?}", e);
1441+
}
1442+
};
14221443
},
14231444
LdkEvent::ConnectionNeeded { node_id, addresses } => {
14241445
let runtime_lock = self.runtime.read().unwrap();

src/ffi/types.rs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
pub use crate::config::{
1414
default_config, AnchorChannelsConfig, BackgroundSyncConfig, ElectrumSyncConfig,
15-
EsploraSyncConfig, MaxDustHTLCExposure,
15+
EsploraSyncConfig, HumanReadableNamesConfig, MaxDustHTLCExposure,
1616
};
1717
pub use crate::graph::{ChannelInfo, ChannelUpdateInfo, NodeAnnouncementInfo, NodeInfo};
1818
pub use crate::liquidity::{LSPS1OrderStatus, LSPS2ServiceConfig, OnchainPaymentInfo, PaymentInfo};
@@ -36,6 +36,8 @@ pub use lightning_invoice::{Description, SignedRawBolt11Invoice};
3636
pub use lightning_liquidity::lsps1::msgs::ChannelInfo as ChannelOrderInfo;
3737
pub use lightning_liquidity::lsps1::msgs::{OrderId, OrderParameters, PaymentState};
3838

39+
pub use lightning::onion_message::dns_resolution::HumanReadableName as LdkHumanReadableName;
40+
3941
pub use bitcoin::{Address, BlockHash, FeeRate, Network, OutPoint, Txid};
4042

4143
pub use bip39::Mnemonic;
@@ -1117,6 +1119,59 @@ impl UniffiCustomTypeConverter for DateTime {
11171119
}
11181120
}
11191121

1122+
pub struct HumanReadableName {
1123+
pub(crate) inner: LdkHumanReadableName,
1124+
}
1125+
1126+
impl HumanReadableName {
1127+
/// Returns the underlying HumanReadableName [`LdkHumanReadableName`]
1128+
pub fn into_inner(&self) -> LdkHumanReadableName {
1129+
self.inner.clone()
1130+
}
1131+
1132+
pub fn from_encoded(encoded: &str) -> Result<Self, Error> {
1133+
let hrn = match LdkHumanReadableName::from_encoded(encoded) {
1134+
Ok(hrn) => Ok(hrn),
1135+
Err(_) => Err(Error::HrnParsingFailed),
1136+
}?;
1137+
1138+
Ok(Self { inner: hrn })
1139+
}
1140+
1141+
pub fn user(&self) -> String {
1142+
self.inner.user().to_string()
1143+
}
1144+
1145+
pub fn domain(&self) -> String {
1146+
self.inner.domain().to_string()
1147+
}
1148+
}
1149+
1150+
impl From<LdkHumanReadableName> for HumanReadableName {
1151+
fn from(ldk_hrn: LdkHumanReadableName) -> Self {
1152+
HumanReadableName { inner: ldk_hrn }
1153+
}
1154+
}
1155+
1156+
impl From<HumanReadableName> for LdkHumanReadableName {
1157+
fn from(wrapper: HumanReadableName) -> Self {
1158+
wrapper.into_inner()
1159+
}
1160+
}
1161+
1162+
impl Deref for HumanReadableName {
1163+
type Target = LdkHumanReadableName;
1164+
fn deref(&self) -> &Self::Target {
1165+
&self.inner
1166+
}
1167+
}
1168+
1169+
impl AsRef<LdkHumanReadableName> for HumanReadableName {
1170+
fn as_ref(&self) -> &LdkHumanReadableName {
1171+
self.deref()
1172+
}
1173+
}
1174+
11201175
#[cfg(test)]
11211176
mod tests {
11221177
use std::{

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,7 @@ impl Node {
874874
Arc::clone(&self.channel_manager),
875875
Arc::clone(&self.payment_store),
876876
Arc::clone(&self.logger),
877+
Arc::clone(&self.config),
877878
)
878879
}
879880

@@ -887,6 +888,7 @@ impl Node {
887888
Arc::clone(&self.channel_manager),
888889
Arc::clone(&self.payment_store),
889890
Arc::clone(&self.logger),
891+
Arc::clone(&self.config),
890892
))
891893
}
892894

0 commit comments

Comments
 (0)