Skip to content

Add support for sending to human-readable names (BIP 353) #528

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ panic = 'abort' # Abort on panic
default = []

[dependencies]
lightning = { version = "0.1.0", features = ["std"] }
lightning = { version = "0.1.0", features = ["std", "dnssec"] }
lightning-types = { version = "0.2.0" }
lightning-invoice = { version = "0.33.0", features = ["std"] }
lightning-net-tokio = { version = "0.1.0" }
Expand Down
19 changes: 18 additions & 1 deletion bindings/ldk_node.udl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ dictionary Config {
u64 probing_liquidity_limit_multiplier;
AnchorChannelsConfig? anchor_channels_config;
SendingParameters? sending_parameters;
HumanReadableNamesConfig? hrn_config;
};

dictionary HumanReadableNamesConfig {
sequence<PublicKey> dns_resolvers_node_ids;
};

dictionary AnchorChannelsConfig {
Expand Down Expand Up @@ -197,6 +202,8 @@ interface Bolt12Payment {
[Throws=NodeError]
PaymentId send_using_amount([ByRef]Offer offer, u64 amount_msat, u64? quantity, string? payer_note);
[Throws=NodeError]
PaymentId send_to_human_readable_name([ByRef]HumanReadableName hrn, u64 amount_msat);
[Throws=NodeError]
Offer receive(u64 amount_msat, [ByRef]string description, u32? expiry_secs, u64? quantity);
[Throws=NodeError]
Offer receive_variable_amount([ByRef]string description, u32? expiry_secs);
Expand Down Expand Up @@ -248,6 +255,13 @@ interface LSPS1Liquidity {
LSPS1OrderStatus check_order_status(OrderId order_id);
};

interface HumanReadableName {
[Throws=NodeError, Name=from_encoded]
constructor([ByRef] string encoded);
string user();
string domain();
};

[Error]
enum NodeError {
"AlreadyRunning",
Expand Down Expand Up @@ -302,6 +316,8 @@ enum NodeError {
"InsufficientFunds",
"LiquiditySourceUnavailable",
"LiquidityFeeTooHigh",
"HrnParsingFailed",
"DnsResolversUnavailable",
};

dictionary NodeStatus {
Expand Down Expand Up @@ -337,6 +353,7 @@ enum BuildError {
"WalletSetupFailed",
"LoggerSetupFailed",
"NetworkMismatch",
"DnsResolversUnavailable",
};

[Trait]
Expand Down Expand Up @@ -402,7 +419,7 @@ interface PaymentKind {
Onchain(Txid txid, ConfirmationStatus status);
Bolt11(PaymentHash hash, PaymentPreimage? preimage, PaymentSecret? secret);
Bolt11Jit(PaymentHash hash, PaymentPreimage? preimage, PaymentSecret? secret, u64? counterparty_skimmed_fee_msat, LSPFeeLimits lsp_fee_limits);
Bolt12Offer(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret, OfferId offer_id, UntrustedString? payer_note, u64? quantity);
Bolt12Offer(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret, OfferId? offer_id, UntrustedString? payer_note, u64? quantity);
Bolt12Refund(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret, UntrustedString? payer_note, u64? quantity);
Spontaneous(PaymentHash hash, PaymentPreimage? preimage);
};
Expand Down
13 changes: 13 additions & 0 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ pub enum BuildError {
LoggerSetupFailed,
/// The given network does not match the node's previously configured network.
NetworkMismatch,
/// The [`dns_resolvers_node_ids`] provided for HRN resolution is empty.
///
/// [`dns_resolvers_node_ids`]: crate::config::HumanReadableNamesConfig::dns_resolvers_node_ids
DnsResolversUnavailable,
}

impl fmt::Display for BuildError {
Expand Down Expand Up @@ -201,6 +205,9 @@ impl fmt::Display for BuildError {
Self::NetworkMismatch => {
write!(f, "Given network does not match the node's previously configured network.")
},
Self::DnsResolversUnavailable => {
write!(f, "The DNS resolvers provided for HRN resolution is empty.")
},
}
}
}
Expand Down Expand Up @@ -1492,6 +1499,12 @@ fn build_with_store_internal(
},
};

if let Some(hrn_config) = &config.hrn_config {
if hrn_config.dns_resolvers_node_ids.is_empty() {
return Err(BuildError::DnsResolversUnavailable);
}
};

let (stop_sender, _) = tokio::sync::watch::channel(());
let (event_handling_stopped_sender, _) = tokio::sync::watch::channel(());

Expand Down
20 changes: 20 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ pub const WALLET_KEYS_SEED_LEN: usize = 64;
/// | `log_level` | Debug |
/// | `anchor_channels_config` | Some(..) |
/// | `sending_parameters` | None |
/// | `hrn_config` | None |
///
/// See [`AnchorChannelsConfig`] and [`SendingParameters`] for more information regarding their
/// respective default values.
Expand Down Expand Up @@ -167,6 +168,10 @@ pub struct Config {
/// **Note:** If unset, default parameters will be used, and you will be able to override the
/// parameters on a per-payment basis in the corresponding method calls.
pub sending_parameters: Option<SendingParameters>,
/// Configuration options for Human-Readable Names ([BIP 353]).
///
/// [BIP 353]: https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki
pub hrn_config: Option<HumanReadableNamesConfig>,
}

impl Default for Config {
Expand All @@ -181,10 +186,24 @@ impl Default for Config {
anchor_channels_config: Some(AnchorChannelsConfig::default()),
sending_parameters: None,
node_alias: None,
hrn_config: None,
}
}
}

/// Configuration options for Human-Readable Names ([BIP 353]).
///
/// [BIP 353]: https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki
#[derive(Debug, Clone)]
pub struct HumanReadableNamesConfig {
/// The DNS resolvers to be used for resolving Human-Readable Names.
///
/// If not empty, the values set will be used as DNS resolvers when sending to HRNs.
///
/// **Note:** If empty, payments to HRNs will fail.
pub dns_resolvers_node_ids: Vec<PublicKey>,
}

/// Configuration options pertaining to 'Anchor' channels, i.e., channels for which the
/// `option_anchors_zero_fee_htlc_tx` channel type is negotiated.
///
Expand Down Expand Up @@ -306,6 +325,7 @@ pub(crate) fn default_user_config(config: &Config) -> UserConfig {
let mut user_config = UserConfig::default();
user_config.channel_handshake_limits.force_announced_channel_preference = false;
user_config.manually_accept_inbound_channels = true;
user_config.manually_handle_bolt12_invoices = true;
user_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx =
config.anchor_channels_config.is_some();

Expand Down
10 changes: 10 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ pub enum Error {
LiquiditySourceUnavailable,
/// The given operation failed due to the LSP's required opening fee being too high.
LiquidityFeeTooHigh,
/// Parsing a Human-Readable Name has failed.
HrnParsingFailed,
/// The given operation failed due to DNS resolvers not being configured.
DnsResolversUnavailable,
}

impl fmt::Display for Error {
Expand Down Expand Up @@ -193,6 +197,12 @@ impl fmt::Display for Error {
Self::LiquidityFeeTooHigh => {
write!(f, "The given operation failed due to the LSP's required opening fee being too high.")
},
Self::HrnParsingFailed => {
write!(f, "Failed to parse a human-readable name.")
},
Self::DnsResolversUnavailable => {
write!(f, "The given operation failed due to DNS resolvers not being configured.")
},
}
}
}
Expand Down
28 changes: 25 additions & 3 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,7 @@ where
hash: Some(payment_hash),
preimage: payment_preimage,
secret: Some(payment_secret),
offer_id,
offer_id: Some(offer_id),
payer_note,
quantity,
};
Expand Down Expand Up @@ -1417,8 +1417,30 @@ where
);
}
},
LdkEvent::InvoiceReceived { .. } => {
debug_assert!(false, "We currently don't handle BOLT12 invoices manually, so this event should never be emitted.");
LdkEvent::InvoiceReceived { payment_id, invoice, context, responder: _ } => {
let update = PaymentDetailsUpdate {
hash: Some(Some(invoice.payment_hash())),
quantity: invoice.quantity(),
..PaymentDetailsUpdate::new(payment_id)
};

match self.payment_store.update(&update) {
Ok(_) => {},
Err(e) => {
log_error!(self.logger, "Failed to access payment store: {}", e);
return Err(ReplayEvent());
},
};

match self
.channel_manager
.send_payment_for_bolt12_invoice(&invoice, context.as_ref())
{
Ok(_) => {},
Err(e) => {
log_error!(self.logger, "Error while paying invoice: {:?}", e);
},
};
},
LdkEvent::ConnectionNeeded { node_id, addresses } => {
let runtime_lock = self.runtime.read().unwrap();
Expand Down
57 changes: 56 additions & 1 deletion src/ffi/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

pub use crate::config::{
default_config, AnchorChannelsConfig, BackgroundSyncConfig, ElectrumSyncConfig,
EsploraSyncConfig, MaxDustHTLCExposure,
EsploraSyncConfig, HumanReadableNamesConfig, MaxDustHTLCExposure,
};
pub use crate::graph::{ChannelInfo, ChannelUpdateInfo, NodeAnnouncementInfo, NodeInfo};
pub use crate::liquidity::{LSPS1OrderStatus, LSPS2ServiceConfig, OnchainPaymentInfo, PaymentInfo};
Expand All @@ -36,6 +36,8 @@ pub use lightning_invoice::{Description, SignedRawBolt11Invoice};
pub use lightning_liquidity::lsps1::msgs::ChannelInfo as ChannelOrderInfo;
pub use lightning_liquidity::lsps1::msgs::{OrderId, OrderParameters, PaymentState};

pub use lightning::onion_message::dns_resolution::HumanReadableName as LdkHumanReadableName;

pub use bitcoin::{Address, BlockHash, FeeRate, Network, OutPoint, Txid};

pub use bip39::Mnemonic;
Expand Down Expand Up @@ -1117,6 +1119,59 @@ impl UniffiCustomTypeConverter for DateTime {
}
}

pub struct HumanReadableName {
pub(crate) inner: LdkHumanReadableName,
}

impl HumanReadableName {
/// Returns the underlying HumanReadableName [`LdkHumanReadableName`]
pub fn into_inner(&self) -> LdkHumanReadableName {
self.inner.clone()
}

pub fn from_encoded(encoded: &str) -> Result<Self, Error> {
let hrn = match LdkHumanReadableName::from_encoded(encoded) {
Ok(hrn) => Ok(hrn),
Err(_) => Err(Error::HrnParsingFailed),
}?;

Ok(Self { inner: hrn })
}

pub fn user(&self) -> String {
self.inner.user().to_string()
}

pub fn domain(&self) -> String {
self.inner.domain().to_string()
}
}

impl From<LdkHumanReadableName> for HumanReadableName {
fn from(ldk_hrn: LdkHumanReadableName) -> Self {
HumanReadableName { inner: ldk_hrn }
}
}

impl From<HumanReadableName> for LdkHumanReadableName {
fn from(wrapper: HumanReadableName) -> Self {
wrapper.into_inner()
}
}

impl Deref for HumanReadableName {
type Target = LdkHumanReadableName;
fn deref(&self) -> &Self::Target {
&self.inner
}
}

impl AsRef<LdkHumanReadableName> for HumanReadableName {
fn as_ref(&self) -> &LdkHumanReadableName {
self.deref()
}
}

#[cfg(test)]
mod tests {
use std::{
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,7 @@ impl Node {
Arc::clone(&self.channel_manager),
Arc::clone(&self.payment_store),
Arc::clone(&self.logger),
Arc::clone(&self.config),
)
}

Expand All @@ -887,6 +888,7 @@ impl Node {
Arc::clone(&self.channel_manager),
Arc::clone(&self.payment_store),
Arc::clone(&self.logger),
Arc::clone(&self.config),
))
}

Expand Down
Loading
Loading