@@ -27,9 +27,9 @@ use std::net::SocketAddr;
2727use std:: path:: PathBuf ;
2828use std:: str:: FromStr ;
2929use std:: sync:: Arc ;
30- use std:: time:: SystemTime ;
30+ use std:: time:: { Duration , SystemTime } ;
3131use tokio:: time;
32- use tokio :: time :: Duration ;
32+
3333use tokio:: { select, try_join} ;
3434use tonic_lnd:: Client ;
3535
@@ -1440,3 +1440,228 @@ async fn test_receive_payment_from_offer_with_multiple_blinded_paths() {
14401440 }
14411441 } ;
14421442}
1443+
1444+ struct MockHrnResolver {
1445+ response_uri : String ,
1446+ }
1447+
1448+ fn set_test_dns_resolver <
1449+ R : bitcoin_payment_instructions:: hrn_resolution:: HrnResolver + Send + Sync + ' static ,
1450+ > (
1451+ resolver : R ,
1452+ ) {
1453+ if let Ok ( mut guard) = lndk:: dns_resolver:: TEST_RESOLVER . write ( ) {
1454+ * guard = Some ( std:: sync:: Arc :: new ( resolver) ) ;
1455+ }
1456+ }
1457+
1458+ fn clear_test_dns_resolver ( ) {
1459+ if let Ok ( mut guard) = lndk:: dns_resolver:: TEST_RESOLVER . write ( ) {
1460+ * guard = None ;
1461+ }
1462+ }
1463+
1464+ impl bitcoin_payment_instructions:: hrn_resolution:: HrnResolver for MockHrnResolver {
1465+ fn resolve_hrn < ' a > (
1466+ & ' a self ,
1467+ _hrn : & ' a bitcoin_payment_instructions:: hrn_resolution:: HumanReadableName ,
1468+ ) -> bitcoin_payment_instructions:: hrn_resolution:: HrnResolutionFuture < ' a > {
1469+ let uri = self . response_uri . clone ( ) ;
1470+ Box :: pin ( async move {
1471+ Ok (
1472+ bitcoin_payment_instructions:: hrn_resolution:: HrnResolution :: DNSSEC {
1473+ proof : None ,
1474+ result : uri,
1475+ } ,
1476+ )
1477+ } )
1478+ }
1479+
1480+ fn resolve_lnurl < ' a > (
1481+ & ' a self ,
1482+ _url : & ' a str ,
1483+ ) -> bitcoin_payment_instructions:: hrn_resolution:: HrnResolutionFuture < ' a > {
1484+ Box :: pin ( async { Err ( "LNURL resolution not supported in mock" ) } )
1485+ }
1486+
1487+ fn resolve_lnurl_to_invoice < ' a > (
1488+ & ' a self ,
1489+ _callback_url : String ,
1490+ _amount : bitcoin_payment_instructions:: amount:: Amount ,
1491+ _expected_description_hash : [ u8 ; 32 ] ,
1492+ ) -> bitcoin_payment_instructions:: hrn_resolution:: LNURLResolutionFuture < ' a > {
1493+ Box :: pin ( async { Err ( "LNURL invoice resolution not supported in mock" ) } )
1494+ }
1495+ }
1496+
1497+ #[ tokio:: test( flavor = "multi_thread" ) ]
1498+ async fn test_pay_offer_with_name_and_dns ( ) {
1499+ use lndk:: lndkrpc:: offers_client:: OffersClient ;
1500+ use lndk:: lndkrpc:: offers_server:: OffersServer ;
1501+ use lndk:: lndkrpc:: PayOfferRequest ;
1502+ use std:: time:: { Duration , SystemTime } ;
1503+ use tonic:: transport:: { Identity , Server , ServerTlsConfig } ;
1504+
1505+ let test_name = "lndk_pay_offer_with_name" ;
1506+ let ( bitcoind, mut lnd, ldk1, ldk2, lndk_dir, _) =
1507+ common:: setup_test_infrastructure ( test_name) . await ;
1508+
1509+ let ( ldk1_pubkey, ldk2_pubkey, _) =
1510+ common:: connect_network ( & ldk1, & ldk2, false , true , & mut lnd, & bitcoind) . await ;
1511+
1512+ let path_pubkeys = vec ! [ ldk2_pubkey, ldk1_pubkey] ;
1513+ let expiration = SystemTime :: now ( ) + Duration :: from_secs ( 24 * 60 * 60 ) ;
1514+ let offer = ldk1
1515+ . create_offer (
1516+ & path_pubkeys,
1517+ Network :: Regtest ,
1518+ 20_000 ,
1519+ Quantity :: One ,
1520+ expiration,
1521+ )
1522+ . await
1523+ . expect ( "should create offer" ) ;
1524+
1525+ let offer_string = offer. to_string ( ) ;
1526+ let human_readable_name = "satoshi@bip353.test" ;
1527+
1528+ let mock_resolver = MockHrnResolver {
1529+ response_uri : format ! ( "bitcoin:?lno={}" , offer_string) ,
1530+ } ;
1531+
1532+ set_test_dns_resolver ( mock_resolver) ;
1533+
1534+ let dns_resolver = lndk:: dns_resolver:: LndkDNSResolverMessageHandler :: new ( ) ;
1535+ let resolved_offer = dns_resolver
1536+ . resolve_name_to_offer ( human_readable_name)
1537+ . await ;
1538+ assert ! (
1539+ resolved_offer. is_ok( ) ,
1540+ "DNS resolution failed: {:?}" ,
1541+ resolved_offer. err( )
1542+ ) ;
1543+ assert_eq ! ( resolved_offer. unwrap( ) , offer_string) ;
1544+
1545+ let ( lndk_cfg, handler, messenger, shutdown) = common:: setup_lndk (
1546+ & lnd. cert_path ,
1547+ & lnd. macaroon_path ,
1548+ lnd. address . clone ( ) ,
1549+ lndk_dir. clone ( ) ,
1550+ )
1551+ . await ;
1552+
1553+ let mut lnd_client = lnd. client . clone ( ) . unwrap ( ) ;
1554+ let info = lnd_client
1555+ . lightning ( )
1556+ . get_info ( tonic_lnd:: lnrpc:: GetInfoRequest { } )
1557+ . await
1558+ . expect ( "failed to get info" )
1559+ . into_inner ( ) ;
1560+
1561+ let server_addr = format ! (
1562+ "{}:{}" ,
1563+ lndk:: DEFAULT_SERVER_HOST ,
1564+ lndk:: DEFAULT_SERVER_PORT
1565+ ) ;
1566+ let server_socket_addr: SocketAddr = server_addr. parse ( ) . unwrap ( ) ;
1567+
1568+ let lnd_cert_path = PathBuf :: from ( & lnd. cert_path ) ;
1569+ let lnd_cert = std:: fs:: read_to_string ( lnd_cert_path) . unwrap ( ) ;
1570+
1571+ let lndk_server = lndk:: server:: LNDKServer :: new (
1572+ handler. clone ( ) ,
1573+ & info. identity_pubkey ,
1574+ lnd_cert. clone ( ) ,
1575+ lnd. address . clone ( ) ,
1576+ )
1577+ . await ;
1578+
1579+ let tls_cert_path = lndk_dir. join ( lndk:: TLS_CERT_FILENAME ) ;
1580+ let tls_key_path = lndk_dir. join ( lndk:: TLS_KEY_FILENAME ) ;
1581+
1582+ if !tls_cert_path. exists ( ) || !tls_key_path. exists ( ) {
1583+ use rcgen:: generate_simple_self_signed;
1584+ let cert_key = generate_simple_self_signed ( vec ! [ "localhost" . to_string( ) ] )
1585+ . expect ( "failed to generate certificate" ) ;
1586+ std:: fs:: write (
1587+ & tls_key_path,
1588+ cert_key. signing_key . serialize_pem ( ) . as_bytes ( ) ,
1589+ )
1590+ . expect ( "failed to write key" ) ;
1591+ std:: fs:: write ( & tls_cert_path, cert_key. cert . pem ( ) ) . expect ( "failed to write cert" ) ;
1592+ }
1593+
1594+ let tls_cert = std:: fs:: read_to_string ( tls_cert_path. clone ( ) ) . unwrap ( ) ;
1595+ let tls_key = std:: fs:: read_to_string ( tls_key_path) . unwrap ( ) ;
1596+ let identity = Identity :: from_pem ( tls_cert. clone ( ) , tls_key) ;
1597+
1598+ let ( server_shutdown, server_listener) = triggered:: trigger ( ) ;
1599+ let server_handle = tokio:: spawn ( async move {
1600+ Server :: builder ( )
1601+ . tls_config ( ServerTlsConfig :: new ( ) . identity ( identity) )
1602+ . expect ( "couldn't configure tls" )
1603+ . add_service ( OffersServer :: new ( lndk_server) )
1604+ . serve_with_shutdown ( server_socket_addr, server_listener)
1605+ . await
1606+ } ) ;
1607+
1608+ tokio:: time:: sleep ( Duration :: from_secs ( 1 ) ) . await ;
1609+
1610+ select ! {
1611+ val = messenger. run( lndk_cfg, Arc :: clone( & handler) ) => {
1612+ panic!( "messenger should not complete first {:?}" , val) ;
1613+ } ,
1614+ result = async {
1615+ let lndk_cert_pem = std:: fs:: read_to_string( tls_cert_path) . unwrap( ) ;
1616+ let macaroon_bytes = std:: fs:: read( & lnd. macaroon_path) . unwrap( ) ;
1617+ let macaroon_hex = hex:: encode( macaroon_bytes) ;
1618+
1619+ let cert = tonic:: transport:: Certificate :: from_pem( lndk_cert_pem. as_bytes( ) ) ;
1620+ let tls_config = tonic:: transport:: ClientTlsConfig :: new( )
1621+ . ca_certificate( cert)
1622+ . domain_name( "localhost" ) ;
1623+
1624+ let channel = tonic:: transport:: Channel :: from_shared( format!( "https://{}" , server_addr) )
1625+ . unwrap( )
1626+ . tls_config( tls_config)
1627+ . unwrap( )
1628+ . connect( )
1629+ . await
1630+ . expect( "failed to connect to lndk server" ) ;
1631+
1632+ let mut client = OffersClient :: new( channel) ;
1633+
1634+ let mut request = tonic:: Request :: new( PayOfferRequest {
1635+ offer: String :: new( ) ,
1636+ amount: Some ( 20_000 ) ,
1637+ payer_note: None ,
1638+ response_invoice_timeout: None ,
1639+ fee_limit: None ,
1640+ fee_limit_percent: None ,
1641+ name: Some ( human_readable_name. to_string( ) ) ,
1642+ } ) ;
1643+
1644+ request. metadata_mut( ) . insert(
1645+ "macaroon" ,
1646+ macaroon_hex. parse( ) . expect( "failed to parse macaroon" ) ,
1647+ ) ;
1648+
1649+ let result = client. pay_offer( request) . await ;
1650+ assert!( result. is_ok( ) , "pay_offer failed: {:?}" , result. err( ) ) ;
1651+
1652+ let response = result. as_ref( ) . unwrap( ) . get_ref( ) ;
1653+ assert!( !response. payment_preimage. is_empty( ) , "Payment should have a preimage" ) ;
1654+ result
1655+ } => {
1656+ assert!( result. is_ok( ) ) ;
1657+
1658+ server_shutdown. trigger( ) ;
1659+ shutdown. trigger( ) ;
1660+ let _ = tokio:: time:: timeout( Duration :: from_secs( 5 ) , server_handle) . await ;
1661+ ldk1. stop( ) . await ;
1662+ ldk2. stop( ) . await ;
1663+
1664+ clear_test_dns_resolver( ) ;
1665+ }
1666+ }
1667+ }
0 commit comments