diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index ff5189f9346..3373828cfc4 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -58,6 +58,9 @@ use lightning::ln::msgs::{ use lightning::ln::script::ShutdownScript; use lightning::ln::types::ChannelId; use lightning::offers::invoice::UnsignedBolt12Invoice; +use lightning::offers::invoice_request::CurrencyConversion; +use lightning::offers::offer::CurrencyCode; +use lightning::offers::parse::Bolt12SemanticError; use lightning::onion_message::messenger::{Destination, MessageRouter, OnionMessagePath}; use lightning::routing::router::{ InFlightHtlcs, Path, PaymentParameters, Route, RouteHop, RouteParameters, Router, @@ -150,6 +153,14 @@ impl MessageRouter for FuzzRouter { } } +struct FuzzCurrencyConversion {} + +impl CurrencyConversion for FuzzCurrencyConversion { + fn fiat_to_msats(&self, _iso4217_code: CurrencyCode) -> Result { + unreachable!() + } +} + pub struct TestBroadcaster {} impl BroadcasterInterface for TestBroadcaster { fn broadcast_transactions(&self, _txs: &[&Transaction]) {} @@ -464,6 +475,7 @@ type ChanMan<'a> = ChannelManager< Arc, &'a FuzzRouter, &'a FuzzRouter, + &'a FuzzCurrencyConversion, Arc, >; @@ -637,6 +649,7 @@ pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { let out = SearchingOutput::new(underlying_out); let broadcast = Arc::new(TestBroadcaster {}); let router = FuzzRouter {}; + let currency_conversion = FuzzCurrencyConversion {}; macro_rules! make_node { ($node_id: expr, $fee_estimator: expr) => {{ @@ -679,6 +692,7 @@ pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { broadcast.clone(), &router, &router, + ¤cy_conversion, Arc::clone(&logger), keys_manager.clone(), keys_manager.clone(), @@ -770,6 +784,7 @@ pub fn do_test(data: &[u8], underlying_out: Out, anchors: bool) { tx_broadcaster: broadcast.clone(), router: &router, message_router: &router, + currency_conversion: ¤cy_conversion, logger, default_config: config, channel_monitors: monitor_refs, diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index eb9d51d487d..99642e8eb7e 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -50,6 +50,9 @@ use lightning::ln::peer_handler::{ use lightning::ln::script::ShutdownScript; use lightning::ln::types::ChannelId; use lightning::offers::invoice::UnsignedBolt12Invoice; +use lightning::offers::invoice_request::CurrencyConversion; +use lightning::offers::offer::CurrencyCode; +use lightning::offers::parse::Bolt12SemanticError; use lightning::onion_message::messenger::{Destination, MessageRouter, OnionMessagePath}; use lightning::routing::gossip::{NetworkGraph, P2PGossipSync}; use lightning::routing::router::{ @@ -181,6 +184,14 @@ impl MessageRouter for FuzzRouter { } } +struct FuzzCurrencyConversion {} + +impl CurrencyConversion for FuzzCurrencyConversion { + fn fiat_to_msats(&self, _iso4217_code: CurrencyCode) -> Result { + unreachable!() + } +} + struct TestBroadcaster { txn_broadcasted: Mutex>, } @@ -236,6 +247,7 @@ type ChannelMan<'a> = ChannelManager< Arc, &'a FuzzRouter, &'a FuzzRouter, + &'a FuzzCurrencyConversion, Arc, >; type PeerMan<'a> = PeerManager< @@ -543,6 +555,7 @@ pub fn do_test(mut data: &[u8], logger: &Arc) { }); let fee_est = Arc::new(FuzzEstimator { input: input.clone() }); let router = FuzzRouter {}; + let currency_conversion = FuzzCurrencyConversion {}; macro_rules! get_slice { ($len: expr) => { @@ -606,6 +619,7 @@ pub fn do_test(mut data: &[u8], logger: &Arc) { broadcast.clone(), &router, &router, + ¤cy_conversion, Arc::clone(&logger), keys_manager.clone(), keys_manager.clone(), diff --git a/fuzz/src/invoice_request_deser.rs b/fuzz/src/invoice_request_deser.rs index 96d8515f0b5..fc66de39565 100644 --- a/fuzz/src/invoice_request_deser.rs +++ b/fuzz/src/invoice_request_deser.rs @@ -10,6 +10,7 @@ use crate::utils::test_logger; use bitcoin::secp256k1::{self, Keypair, Parity, PublicKey, Secp256k1, SecretKey}; use core::convert::TryFrom; +use core::ops::Deref; use lightning::blinded_path::payment::{ BlindedPaymentPath, Bolt12OfferContext, ForwardTlvs, PaymentConstraints, PaymentContext, PaymentForwardNode, PaymentRelay, UnauthenticatedReceiveTlvs, @@ -17,9 +18,11 @@ use lightning::blinded_path::payment::{ use lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA; use lightning::ln::inbound_payment::ExpandedKey; use lightning::offers::invoice::UnsignedBolt12Invoice; -use lightning::offers::invoice_request::{InvoiceRequest, InvoiceRequestFields}; +use lightning::offers::invoice_request::{ + CurrencyConversion, InvoiceRequest, InvoiceRequestFields, +}; use lightning::offers::nonce::Nonce; -use lightning::offers::offer::OfferId; +use lightning::offers::offer::{CurrencyCode, OfferId}; use lightning::offers::parse::Bolt12SemanticError; use lightning::sign::EntropySource; use lightning::types::features::BlindedHopFeatures; @@ -79,6 +82,22 @@ fn privkey(byte: u8) -> SecretKey { SecretKey::from_slice(&[byte; 32]).unwrap() } +struct FuzzCurrencyConversion; + +impl Deref for FuzzCurrencyConversion { + type Target = Self; + + fn deref(&self) -> &Self::Target { + self + } +} + +impl CurrencyConversion for FuzzCurrencyConversion { + fn fiat_to_msats(&self, _iso4217_code: CurrencyCode) -> Result { + unreachable!() + } +} + fn build_response( invoice_request: &InvoiceRequest, secp_ctx: &Secp256k1, ) -> Result { @@ -145,7 +164,9 @@ fn build_response( .unwrap(); let payment_hash = PaymentHash([42; 32]); - invoice_request.respond_with(vec![payment_path], payment_hash)?.build() + invoice_request + .respond_with(&FuzzCurrencyConversion {}, vec![payment_path], payment_hash)? + .build() } pub fn invoice_request_deser_test(data: &[u8], out: Out) { diff --git a/fuzz/src/lsps_message.rs b/fuzz/src/lsps_message.rs index 299b9f07955..c134da4a97c 100644 --- a/fuzz/src/lsps_message.rs +++ b/fuzz/src/lsps_message.rs @@ -10,6 +10,7 @@ use lightning::chain::{chainmonitor, BestBlock}; use lightning::ln::channelmanager::{ChainParameters, ChannelManager}; use lightning::ln::peer_handler::CustomMessageHandler; use lightning::ln::wire::CustomMessageReader; +use lightning::offers::invoice_request::DefaultCurrencyConversion; use lightning::onion_message::messenger::DefaultMessageRouter; use lightning::routing::gossip::NetworkGraph; use lightning::routing::router::DefaultRouter; @@ -49,6 +50,7 @@ pub fn do_test(data: &[u8]) { )); let msg_router = Arc::new(DefaultMessageRouter::new(Arc::clone(&network_graph), Arc::clone(&keys_manager))); + let currency_conversion = Arc::new(DefaultCurrencyConversion {}); let chain_source = Arc::new(TestChainSource::new(Network::Bitcoin)); let kv_store = Arc::new(TestStore::new(false)); let chain_monitor = Arc::new(chainmonitor::ChainMonitor::new( @@ -68,6 +70,7 @@ pub fn do_test(data: &[u8]) { Arc::clone(&tx_broadcaster), Arc::clone(&router), Arc::clone(&msg_router), + Arc::clone(¤cy_conversion), Arc::clone(&logger), Arc::clone(&keys_manager), Arc::clone(&keys_manager), diff --git a/lightning-background-processor/src/lib.rs b/lightning-background-processor/src/lib.rs index 1376f43a349..ab8b553f7b6 100644 --- a/lightning-background-processor/src/lib.rs +++ b/lightning-background-processor/src/lib.rs @@ -1672,6 +1672,7 @@ mod tests { IgnoringMessageHandler, MessageHandler, PeerManager, SocketDescriptor, }; use lightning::ln::types::ChannelId; + use lightning::offers::invoice_request::DefaultCurrencyConversion; use lightning::onion_message::messenger::{DefaultMessageRouter, OnionMessenger}; use lightning::routing::gossip::{NetworkGraph, P2PGossipSync}; use lightning::routing::router::{CandidateRouteHop, DefaultRouter, Path, RouteHop}; @@ -1745,6 +1746,7 @@ mod tests { Arc, >, >, + Arc, Arc, >; @@ -2167,6 +2169,7 @@ mod tests { Arc::clone(&network_graph), Arc::clone(&keys_manager), )); + let currency_conversion = Arc::new(DefaultCurrencyConversion {}); let chain_source = Arc::new(test_utils::TestChainSource::new(Network::Bitcoin)); let kv_store = Arc::new(Persister::new(format!("{}_persister_{}", &persist_dir, i).into())); @@ -2189,6 +2192,7 @@ mod tests { Arc::clone(&tx_broadcaster), Arc::clone(&router), Arc::clone(&msg_router), + currency_conversion, Arc::clone(&logger), Arc::clone(&keys_manager), Arc::clone(&keys_manager), diff --git a/lightning-block-sync/src/init.rs b/lightning-block-sync/src/init.rs index f71a72456dc..ff67da663b5 100644 --- a/lightning-block-sync/src/init.rs +++ b/lightning-block-sync/src/init.rs @@ -50,6 +50,7 @@ where /// use lightning::chain::chaininterface::BroadcasterInterface; /// use lightning::chain::chaininterface::FeeEstimator; /// use lightning::ln::channelmanager::{ChannelManager, ChannelManagerReadArgs}; +/// use lightning::offers::invoice_request::CurrencyConversion; /// use lightning::onion_message::messenger::MessageRouter; /// use lightning::routing::router::Router; /// use lightning::sign; @@ -71,6 +72,7 @@ where /// F: FeeEstimator, /// R: Router, /// MR: MessageRouter, +/// CC: CurrencyConversion, /// L: Logger, /// C: chain::Filter, /// P: chainmonitor::Persist, @@ -85,6 +87,7 @@ where /// fee_estimator: &F, /// router: &R, /// message_router: &MR, +/// currency_conversion: &CC, /// logger: &L, /// persister: &P, /// ) { @@ -105,11 +108,12 @@ where /// tx_broadcaster, /// router, /// message_router, +/// currency_conversion, /// logger, /// config, /// vec![&mut monitor], /// ); -/// <(BlockHash, ChannelManager<&ChainMonitor, &T, &ES, &NS, &SP, &F, &R, &MR, &L>)>::read( +/// <(BlockHash, ChannelManager<&ChainMonitor, &T, &ES, &NS, &SP, &F, &R, &MR, &CC, &L>)>::read( /// &mut Cursor::new(&serialized_manager), read_args).unwrap() /// }; /// diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 6f411273aab..ba96eded954 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -96,7 +96,9 @@ use crate::offers::invoice::{ Bolt12Invoice, DerivedSigningPubkey, InvoiceBuilder, DEFAULT_RELATIVE_EXPIRY, }; use crate::offers::invoice_error::InvoiceError; -use crate::offers::invoice_request::InvoiceRequest; +#[cfg(not(c_bindings))] +use crate::offers::invoice_request::DefaultCurrencyConversion; +use crate::offers::invoice_request::{CurrencyConversion, InvoiceRequest}; use crate::offers::nonce::Nonce; use crate::offers::offer::Offer; use crate::offers::parse::Bolt12SemanticError; @@ -1563,6 +1565,7 @@ pub type SimpleArcChannelManager = ChannelManager< >, >, Arc>>, Arc, Arc>>, + Arc, Arc, >; @@ -1578,24 +1581,26 @@ pub type SimpleArcChannelManager = ChannelManager< /// /// This is not exported to bindings users as type aliases aren't supported in most languages. #[cfg(not(c_bindings))] -pub type SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, M, T, F, L> = ChannelManager< - &'a M, - &'b T, - &'c KeysManager, - &'c KeysManager, - &'c KeysManager, - &'d F, - &'e DefaultRouter< - &'f NetworkGraph<&'g L>, - &'g L, +pub type SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, M, T, F, L> = + ChannelManager< + &'a M, + &'b T, &'c KeysManager, - &'h RwLock, &'g L>>, - ProbabilisticScoringFeeParameters, - ProbabilisticScorer<&'f NetworkGraph<&'g L>, &'g L>, - >, - &'i DefaultMessageRouter<&'f NetworkGraph<&'g L>, &'g L, &'c KeysManager>, - &'g L, ->; + &'c KeysManager, + &'c KeysManager, + &'d F, + &'e DefaultRouter< + &'f NetworkGraph<&'g L>, + &'g L, + &'c KeysManager, + &'h RwLock, &'g L>>, + ProbabilisticScoringFeeParameters, + ProbabilisticScorer<&'f NetworkGraph<&'g L>, &'g L>, + >, + &'i DefaultMessageRouter<&'f NetworkGraph<&'g L>, &'g L, &'c KeysManager>, + &'j DefaultCurrencyConversion, + &'g L, + >; /// A trivial trait which describes any [`ChannelManager`]. /// @@ -1636,6 +1641,10 @@ pub trait AChannelManager { type MessageRouter: MessageRouter + ?Sized; /// A type that may be dereferenced to [`Self::MessageRouter`]. type MR: Deref; + /// A type implementing [`CurrencyConversion`]. + type CurrencyConversion: CurrencyConversion + ?Sized; + /// A type that may be dereferenced to [`Self::CurrencyConversion`]. + type CC: Deref; /// A type implementing [`Logger`]. type Logger: Logger + ?Sized; /// A type that may be dereferenced to [`Self::Logger`]. @@ -1652,6 +1661,7 @@ pub trait AChannelManager { Self::F, Self::R, Self::MR, + Self::CC, Self::L, >; } @@ -1665,8 +1675,9 @@ impl< F: Deref, R: Deref, MR: Deref, + CC: Deref, L: Deref, - > AChannelManager for ChannelManager + > AChannelManager for ChannelManager where M::Target: chain::Watch<::EcdsaSigner>, T::Target: BroadcasterInterface, @@ -1676,6 +1687,7 @@ where F::Target: FeeEstimator, R::Target: Router, MR::Target: MessageRouter, + CC::Target: CurrencyConversion, L::Target: Logger, { type Watch = M::Target; @@ -1695,9 +1707,11 @@ where type R = R; type MessageRouter = MR::Target; type MR = MR; + type CurrencyConversion = CC::Target; + type CC = CC; type Logger = L::Target; type L = L; - fn get_cm(&self) -> &ChannelManager { + fn get_cm(&self) -> &ChannelManager { self } } @@ -1778,6 +1792,7 @@ where /// # tx_broadcaster: &dyn lightning::chain::chaininterface::BroadcasterInterface, /// # router: &lightning::routing::router::DefaultRouter<&NetworkGraph<&'a L>, &'a L, &ES, &S, SP, SL>, /// # message_router: &lightning::onion_message::messenger::DefaultMessageRouter<&NetworkGraph<&'a L>, &'a L, &ES>, +/// # currency_conversion: &lightning::offers::invoice_request::DefaultCurrencyConversion, /// # logger: &L, /// # entropy_source: &ES, /// # node_signer: &dyn lightning::sign::NodeSigner, @@ -1793,7 +1808,7 @@ where /// }; /// let default_config = UserConfig::default(); /// let channel_manager = ChannelManager::new( -/// fee_estimator, chain_monitor, tx_broadcaster, router, message_router, logger, +/// fee_estimator, chain_monitor, tx_broadcaster, router, message_router, currency_conversion, logger, /// entropy_source, node_signer, signer_provider, default_config.clone(), params, current_timestamp, /// ); /// @@ -1801,10 +1816,10 @@ where /// let mut channel_monitors = read_channel_monitors(); /// let args = ChannelManagerReadArgs::new( /// entropy_source, node_signer, signer_provider, fee_estimator, chain_monitor, tx_broadcaster, -/// router, message_router, logger, default_config, channel_monitors.iter().collect(), +/// router, message_router, currency_conversion, logger, default_config, channel_monitors.iter().collect(), /// ); /// let (block_hash, channel_manager) = -/// <(BlockHash, ChannelManager<_, _, _, _, _, _, _, _, _>)>::read(&mut reader, args)?; +/// <(BlockHash, ChannelManager<_, _, _, _, _, _, _, _, _, _>)>::read(&mut reader, args)?; /// /// // Update the ChannelManager and ChannelMonitors with the latest chain data /// // ... @@ -2492,6 +2507,7 @@ pub struct ChannelManager< F: Deref, R: Deref, MR: Deref, + CC: Deref, L: Deref, > where M::Target: chain::Watch<::EcdsaSigner>, @@ -2502,6 +2518,7 @@ pub struct ChannelManager< F::Target: FeeEstimator, R::Target: Router, MR::Target: MessageRouter, + CC::Target: CurrencyConversion, L::Target: Logger, { default_configuration: UserConfig, @@ -2512,9 +2529,9 @@ pub struct ChannelManager< router: R, #[cfg(test)] - pub(super) flow: OffersMessageFlow, + pub(super) flow: OffersMessageFlow, #[cfg(not(test))] - flow: OffersMessageFlow, + flow: OffersMessageFlow, /// See `ChannelManager` struct-level documentation for lock order requirements. #[cfg(any(test, feature = "_test_utils"))] @@ -3677,8 +3694,9 @@ impl< F: Deref, R: Deref, MR: Deref, + CC: Deref, L: Deref, - > ChannelManager + > ChannelManager where M::Target: chain::Watch<::EcdsaSigner>, T::Target: BroadcasterInterface, @@ -3688,6 +3706,7 @@ where F::Target: FeeEstimator, R::Target: Router, MR::Target: MessageRouter, + CC::Target: CurrencyConversion, L::Target: Logger, { /// Constructs a new `ChannelManager` to hold several channels and route between them. @@ -3709,9 +3728,10 @@ where /// [`params.best_block.block_hash`]: chain::BestBlock::block_hash #[rustfmt::skip] pub fn new( - fee_est: F, chain_monitor: M, tx_broadcaster: T, router: R, message_router: MR, logger: L, - entropy_source: ES, node_signer: NS, signer_provider: SP, config: UserConfig, - params: ChainParameters, current_timestamp: u32, + fee_est: F, chain_monitor: M, tx_broadcaster: T, router: R, message_router: MR, + currency_conversion: CC, logger: L, entropy_source: ES, node_signer: NS, + signer_provider: SP, config: UserConfig, params: ChainParameters, + current_timestamp: u32, ) -> Self { let mut secp_ctx = Secp256k1::new(); secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes()); @@ -3722,7 +3742,7 @@ where let flow = OffersMessageFlow::new( ChainHash::using_genesis_block(params.network), params.best_block, our_network_pubkey, current_timestamp, expanded_inbound_key, - node_signer.get_receive_auth_key(), secp_ctx.clone(), message_router + node_signer.get_receive_auth_key(), secp_ctx.clone(), message_router, currency_conversion, ); ChannelManager { @@ -5283,6 +5303,7 @@ where let features = self.bolt12_invoice_features(); let outbound_pmts_res = self.pending_outbound_payments.static_invoice_received( invoice, + &self.flow.currency_conversion, payment_id, features, best_block_height, @@ -11842,8 +11863,9 @@ impl< F: Deref, R: Deref, MR: Deref, + CC: Deref, L: Deref, - > ChannelManager + > ChannelManager where M::Target: chain::Watch<::EcdsaSigner>, T::Target: BroadcasterInterface, @@ -11853,6 +11875,7 @@ where F::Target: FeeEstimator, R::Target: Router, MR::Target: MessageRouter, + CC::Target: CurrencyConversion, L::Target: Logger, { #[cfg(not(c_bindings))] @@ -12666,8 +12689,9 @@ impl< F: Deref, R: Deref, MR: Deref, + CC: Deref, L: Deref, - > BaseMessageHandler for ChannelManager + > BaseMessageHandler for ChannelManager where M::Target: chain::Watch<::EcdsaSigner>, T::Target: BroadcasterInterface, @@ -12677,6 +12701,7 @@ where F::Target: FeeEstimator, R::Target: Router, MR::Target: MessageRouter, + CC::Target: CurrencyConversion, L::Target: Logger, { fn provided_node_features(&self) -> NodeFeatures { @@ -12976,8 +13001,9 @@ impl< F: Deref, R: Deref, MR: Deref, + CC: Deref, L: Deref, - > EventsProvider for ChannelManager + > EventsProvider for ChannelManager where M::Target: chain::Watch<::EcdsaSigner>, T::Target: BroadcasterInterface, @@ -12987,6 +13013,7 @@ where F::Target: FeeEstimator, R::Target: Router, MR::Target: MessageRouter, + CC::Target: CurrencyConversion, L::Target: Logger, { /// Processes events that must be periodically handled. @@ -13011,8 +13038,9 @@ impl< F: Deref, R: Deref, MR: Deref, + CC: Deref, L: Deref, - > chain::Listen for ChannelManager + > chain::Listen for ChannelManager where M::Target: chain::Watch<::EcdsaSigner>, T::Target: BroadcasterInterface, @@ -13022,6 +13050,7 @@ where F::Target: FeeEstimator, R::Target: Router, MR::Target: MessageRouter, + CC::Target: CurrencyConversion, L::Target: Logger, { fn filtered_block_connected(&self, header: &Header, txdata: &TransactionData, height: u32) { @@ -13075,8 +13104,9 @@ impl< F: Deref, R: Deref, MR: Deref, + CC: Deref, L: Deref, - > chain::Confirm for ChannelManager + > chain::Confirm for ChannelManager where M::Target: chain::Watch<::EcdsaSigner>, T::Target: BroadcasterInterface, @@ -13086,6 +13116,7 @@ where F::Target: FeeEstimator, R::Target: Router, MR::Target: MessageRouter, + CC::Target: CurrencyConversion, L::Target: Logger, { #[rustfmt::skip] @@ -13232,8 +13263,9 @@ impl< F: Deref, R: Deref, MR: Deref, + CC: Deref, L: Deref, - > ChannelManager + > ChannelManager where M::Target: chain::Watch<::EcdsaSigner>, T::Target: BroadcasterInterface, @@ -13243,6 +13275,7 @@ where F::Target: FeeEstimator, R::Target: Router, MR::Target: MessageRouter, + CC::Target: CurrencyConversion, L::Target: Logger, { /// Calls a function which handles an on-chain event (blocks dis/connected, transactions @@ -13564,8 +13597,9 @@ impl< F: Deref, R: Deref, MR: Deref, + CC: Deref, L: Deref, - > ChannelMessageHandler for ChannelManager + > ChannelMessageHandler for ChannelManager where M::Target: chain::Watch<::EcdsaSigner>, T::Target: BroadcasterInterface, @@ -13575,6 +13609,7 @@ where F::Target: FeeEstimator, R::Target: Router, MR::Target: MessageRouter, + CC::Target: CurrencyConversion, L::Target: Logger, { fn handle_open_channel(&self, counterparty_node_id: PublicKey, message: &msgs::OpenChannel) { @@ -14139,8 +14174,9 @@ impl< F: Deref, R: Deref, MR: Deref, + CC: Deref, L: Deref, - > OffersMessageHandler for ChannelManager + > OffersMessageHandler for ChannelManager where M::Target: chain::Watch<::EcdsaSigner>, T::Target: BroadcasterInterface, @@ -14150,6 +14186,7 @@ where F::Target: FeeEstimator, R::Target: Router, MR::Target: MessageRouter, + CC::Target: CurrencyConversion, L::Target: Logger, { #[rustfmt::skip] @@ -14214,7 +14251,7 @@ where }; let amount_msats = match InvoiceBuilder::::amount_msats( - &invoice_request.inner + &invoice_request.inner, &self.flow.currency_conversion ) { Ok(amount_msats) => amount_msats, Err(error) => return Some((OffersMessage::InvoiceError(error.into()), responder.respond())), @@ -14314,8 +14351,9 @@ impl< F: Deref, R: Deref, MR: Deref, + CC: Deref, L: Deref, - > AsyncPaymentsMessageHandler for ChannelManager + > AsyncPaymentsMessageHandler for ChannelManager where M::Target: chain::Watch<::EcdsaSigner>, T::Target: BroadcasterInterface, @@ -14325,6 +14363,7 @@ where F::Target: FeeEstimator, R::Target: Router, MR::Target: MessageRouter, + CC::Target: CurrencyConversion, L::Target: Logger, { fn handle_offer_paths_request( @@ -14470,8 +14509,9 @@ impl< F: Deref, R: Deref, MR: Deref, + CC: Deref, L: Deref, - > DNSResolverMessageHandler for ChannelManager + > DNSResolverMessageHandler for ChannelManager where M::Target: chain::Watch<::EcdsaSigner>, T::Target: BroadcasterInterface, @@ -14481,6 +14521,7 @@ where F::Target: FeeEstimator, R::Target: Router, MR::Target: MessageRouter, + CC::Target: CurrencyConversion, L::Target: Logger, { fn handle_dnssec_query( @@ -14543,8 +14584,9 @@ impl< F: Deref, R: Deref, MR: Deref, + CC: Deref, L: Deref, - > NodeIdLookUp for ChannelManager + > NodeIdLookUp for ChannelManager where M::Target: chain::Watch<::EcdsaSigner>, T::Target: BroadcasterInterface, @@ -14554,6 +14596,7 @@ where F::Target: FeeEstimator, R::Target: Router, MR::Target: MessageRouter, + CC::Target: CurrencyConversion, L::Target: Logger, { fn next_node_id(&self, short_channel_id: u64) -> Option { @@ -15053,8 +15096,9 @@ impl< F: Deref, R: Deref, MR: Deref, + CC: Deref, L: Deref, - > Writeable for ChannelManager + > Writeable for ChannelManager where M::Target: chain::Watch<::EcdsaSigner>, T::Target: BroadcasterInterface, @@ -15064,6 +15108,7 @@ where F::Target: FeeEstimator, R::Target: Router, MR::Target: MessageRouter, + CC::Target: CurrencyConversion, L::Target: Logger, { #[rustfmt::skip] @@ -15387,6 +15432,7 @@ pub struct ChannelManagerReadArgs< F: Deref, R: Deref, MR: Deref, + CC: Deref, L: Deref, > where M::Target: chain::Watch<::EcdsaSigner>, @@ -15397,6 +15443,7 @@ pub struct ChannelManagerReadArgs< F::Target: FeeEstimator, R::Target: Router, MR::Target: MessageRouter, + CC::Target: CurrencyConversion, L::Target: Logger, { /// A cryptographically secure source of entropy. @@ -15435,6 +15482,8 @@ pub struct ChannelManagerReadArgs< /// /// [`BlindedMessagePath`]: crate::blinded_path::message::BlindedMessagePath pub message_router: MR, + /// The CurrencyConversion which will be used in the OffersMessageFlow to convert fiat to msats. + pub currency_conversion: CC, /// The Logger for use in the ChannelManager and which may be used to log information during /// deserialization. pub logger: L, @@ -15467,8 +15516,9 @@ impl< F: Deref, R: Deref, MR: Deref, + CC: Deref, L: Deref, - > ChannelManagerReadArgs<'a, M, T, ES, NS, SP, F, R, MR, L> + > ChannelManagerReadArgs<'a, M, T, ES, NS, SP, F, R, MR, CC, L> where M::Target: chain::Watch<::EcdsaSigner>, T::Target: BroadcasterInterface, @@ -15478,6 +15528,7 @@ where F::Target: FeeEstimator, R::Target: Router, MR::Target: MessageRouter, + CC::Target: CurrencyConversion, L::Target: Logger, { /// Simple utility function to create a ChannelManagerReadArgs which creates the monitor @@ -15485,8 +15536,8 @@ where /// populate a HashMap directly from C. pub fn new( entropy_source: ES, node_signer: NS, signer_provider: SP, fee_estimator: F, - chain_monitor: M, tx_broadcaster: T, router: R, message_router: MR, logger: L, - default_config: UserConfig, + chain_monitor: M, tx_broadcaster: T, router: R, message_router: MR, + currency_conversion: CC, logger: L, default_config: UserConfig, mut channel_monitors: Vec<&'a ChannelMonitor<::EcdsaSigner>>, ) -> Self { Self { @@ -15498,6 +15549,7 @@ where tx_broadcaster, router, message_router, + currency_conversion, logger, default_config, channel_monitors: hash_map_from_iter( @@ -15519,9 +15571,10 @@ impl< F: Deref, R: Deref, MR: Deref, + CC: Deref, L: Deref, - > ReadableArgs> - for (BlockHash, Arc>) + > ReadableArgs> + for (BlockHash, Arc>) where M::Target: chain::Watch<::EcdsaSigner>, T::Target: BroadcasterInterface, @@ -15531,13 +15584,14 @@ where F::Target: FeeEstimator, R::Target: Router, MR::Target: MessageRouter, + CC::Target: CurrencyConversion, L::Target: Logger, { fn read( - reader: &mut Reader, args: ChannelManagerReadArgs<'a, M, T, ES, NS, SP, F, R, MR, L>, + reader: &mut Reader, args: ChannelManagerReadArgs<'a, M, T, ES, NS, SP, F, R, MR, CC, L>, ) -> Result { let (blockhash, chan_manager) = - <(BlockHash, ChannelManager)>::read(reader, args)?; + <(BlockHash, ChannelManager)>::read(reader, args)?; Ok((blockhash, Arc::new(chan_manager))) } } @@ -15552,9 +15606,10 @@ impl< F: Deref, R: Deref, MR: Deref, + CC: Deref, L: Deref, - > ReadableArgs> - for (BlockHash, ChannelManager) + > ReadableArgs> + for (BlockHash, ChannelManager) where M::Target: chain::Watch<::EcdsaSigner>, T::Target: BroadcasterInterface, @@ -15564,10 +15619,12 @@ where F::Target: FeeEstimator, R::Target: Router, MR::Target: MessageRouter, + CC::Target: CurrencyConversion, L::Target: Logger, { fn read( - reader: &mut Reader, mut args: ChannelManagerReadArgs<'a, M, T, ES, NS, SP, F, R, MR, L>, + reader: &mut Reader, + mut args: ChannelManagerReadArgs<'a, M, T, ES, NS, SP, F, R, MR, CC, L>, ) -> Result { let _ver = read_ver_prefix!(reader, SERIALIZATION_VERSION); @@ -16807,6 +16864,7 @@ where args.node_signer.get_receive_auth_key(), secp_ctx.clone(), args.message_router, + args.currency_conversion, ) .with_async_payments_offers_cache(async_receive_offer_cache); @@ -18446,6 +18504,7 @@ pub mod bench { &'a test_utils::TestFeeEstimator, &'a test_utils::TestRouter<'a>, &'a test_utils::TestMessageRouter<'a>, + &'a test_utils::TestCurrencyConversion, &'a test_utils::TestLogger, >; @@ -18484,6 +18543,7 @@ pub mod bench { let entropy = test_utils::TestKeysInterface::new(&[0u8; 32], network); let router = test_utils::TestRouter::new(Arc::new(NetworkGraph::new(network, &logger_a)), &logger_a, &scorer); let message_router = test_utils::TestMessageRouter::new_default(Arc::new(NetworkGraph::new(network, &logger_a)), &entropy); + let currency_converter = test_utils::TestCurrencyConversion::new(); let mut config: UserConfig = Default::default(); config.channel_config.max_dust_htlc_exposure = MaxDustHTLCExposure::FeeRateMultiplier(5_000_000 / 253); @@ -18492,7 +18552,7 @@ pub mod bench { let seed_a = [1u8; 32]; let keys_manager_a = KeysManager::new(&seed_a, 42, 42); let chain_monitor_a = ChainMonitor::new(None, &tx_broadcaster, &logger_a, &fee_estimator, &persister_a, &keys_manager_a, keys_manager_a.get_peer_storage_key()); - let node_a = ChannelManager::new(&fee_estimator, &chain_monitor_a, &tx_broadcaster, &router, &message_router, &logger_a, &keys_manager_a, &keys_manager_a, &keys_manager_a, config.clone(), ChainParameters { + let node_a = ChannelManager::new(&fee_estimator, &chain_monitor_a, &tx_broadcaster, &router, &message_router, ¤cy_converter, &logger_a, &keys_manager_a, &keys_manager_a, &keys_manager_a, config.clone(), ChainParameters { network, best_block: BestBlock::from_network(network), }, genesis_block.header.time); @@ -18502,7 +18562,7 @@ pub mod bench { let seed_b = [2u8; 32]; let keys_manager_b = KeysManager::new(&seed_b, 42, 42); let chain_monitor_b = ChainMonitor::new(None, &tx_broadcaster, &logger_a, &fee_estimator, &persister_b, &keys_manager_b, keys_manager_b.get_peer_storage_key()); - let node_b = ChannelManager::new(&fee_estimator, &chain_monitor_b, &tx_broadcaster, &router, &message_router, &logger_b, &keys_manager_b, &keys_manager_b, &keys_manager_b, config.clone(), ChainParameters { + let node_b = ChannelManager::new(&fee_estimator, &chain_monitor_b, &tx_broadcaster, &router, &message_router, ¤cy_converter, &logger_b, &keys_manager_b, &keys_manager_b, &keys_manager_b, config.clone(), ChainParameters { network, best_block: BestBlock::from_network(network), }, genesis_block.header.time); diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 00e883e8561..40b59be2071 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -478,6 +478,7 @@ pub struct NodeCfg<'a> { pub fee_estimator: &'a test_utils::TestFeeEstimator, pub router: test_utils::TestRouter<'a>, pub message_router: test_utils::TestMessageRouter<'a>, + pub currency_conversion: test_utils::TestCurrencyConversion, pub chain_monitor: test_utils::TestChainMonitor<'a>, pub keys_manager: &'a test_utils::TestKeysInterface, pub logger: &'a test_utils::TestLogger, @@ -495,6 +496,7 @@ pub type TestChannelManager<'node_cfg, 'chan_mon_cfg> = ChannelManager< &'chan_mon_cfg test_utils::TestFeeEstimator, &'node_cfg test_utils::TestRouter<'chan_mon_cfg>, &'node_cfg test_utils::TestMessageRouter<'chan_mon_cfg>, + &'node_cfg test_utils::TestCurrencyConversion, &'chan_mon_cfg test_utils::TestLogger, >; @@ -543,6 +545,7 @@ pub struct Node<'chan_man, 'node_cfg: 'chan_man, 'chan_mon_cfg: 'node_cfg> { pub fee_estimator: &'chan_mon_cfg test_utils::TestFeeEstimator, pub router: &'node_cfg test_utils::TestRouter<'chan_mon_cfg>, pub message_router: &'node_cfg test_utils::TestMessageRouter<'chan_mon_cfg>, + pub currency_conversion: &'node_cfg test_utils::TestCurrencyConversion, pub chain_monitor: &'node_cfg test_utils::TestChainMonitor<'chan_mon_cfg>, pub keys_manager: &'chan_mon_cfg test_utils::TestKeysInterface, pub node: &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>, @@ -716,6 +719,7 @@ pub trait NodeHolder { ::F, ::R, ::MR, + ::CC, ::L, >; fn chain_monitor(&self) -> Option<&test_utils::TestChainMonitor>; @@ -733,6 +737,7 @@ impl NodeHolder for &H { ::F, ::R, ::MR, + ::CC, ::L, > { (*self).node() @@ -862,6 +867,7 @@ impl<'a, 'b, 'c> Drop for Node<'a, 'b, 'c> { &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestMessageRouter, + &test_utils::TestCurrencyConversion, &test_utils::TestLogger, >, )>::read( @@ -881,6 +887,7 @@ impl<'a, 'b, 'c> Drop for Node<'a, 'b, 'c> { network_graph, self.keys_manager, ), + currency_conversion: &test_utils::TestCurrencyConversion::new(), chain_monitor: self.chain_monitor, tx_broadcaster: &broadcaster, logger: &self.logger, @@ -1311,6 +1318,7 @@ pub fn _reload_node<'a, 'b, 'c>( fee_estimator: node.fee_estimator, router: node.router, message_router: node.message_router, + currency_conversion: node.currency_conversion, chain_monitor: node.chain_monitor, tx_broadcaster: node.tx_broadcaster, logger: node.logger, @@ -4194,6 +4202,7 @@ where Arc::clone(&network_graph), &cfg.keys_manager, ), + currency_conversion: test_utils::TestCurrencyConversion::new(), chain_monitor, keys_manager: &cfg.keys_manager, node_seed: seed, @@ -4271,6 +4280,7 @@ pub fn create_node_chanmgrs<'a, 'b>( &'b test_utils::TestFeeEstimator, &'a test_utils::TestRouter<'b>, &'a test_utils::TestMessageRouter<'b>, + &'a test_utils::TestCurrencyConversion, &'b test_utils::TestLogger, >, > { @@ -4285,6 +4295,7 @@ pub fn create_node_chanmgrs<'a, 'b>( cfgs[i].tx_broadcaster, &cfgs[i].router, &cfgs[i].message_router, + &cfgs[i].currency_conversion, cfgs[i].logger, cfgs[i].keys_manager, cfgs[i].keys_manager, @@ -4315,6 +4326,7 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>( &'c test_utils::TestFeeEstimator, &'c test_utils::TestRouter, &'c test_utils::TestMessageRouter, + &'c test_utils::TestCurrencyConversion, &'c test_utils::TestLogger, >, >, @@ -4361,6 +4373,7 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>( fee_estimator: cfgs[i].fee_estimator, router: &cfgs[i].router, message_router: &cfgs[i].message_router, + currency_conversion: &cfgs[i].currency_conversion, chain_monitor: &cfgs[i].chain_monitor, keys_manager: &cfgs[i].keys_manager, node: &chan_mgrs[i], diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 388db6d174f..879e747b455 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -5152,6 +5152,7 @@ pub fn test_key_derivation_params() { test_utils::TestRouter::new(Arc::clone(&network_graph), &chanmon_cfgs[0].logger, &scorer); let message_router = test_utils::TestMessageRouter::new_default(Arc::clone(&network_graph), &keys_manager); + let currency_conversion = test_utils::TestCurrencyConversion::new(); let node = NodeCfg { chain_source: &chanmon_cfgs[0].chain_source, logger: &chanmon_cfgs[0].logger, @@ -5159,6 +5160,7 @@ pub fn test_key_derivation_params() { fee_estimator: &chanmon_cfgs[0].fee_estimator, router, message_router, + currency_conversion, chain_monitor, keys_manager: &keys_manager, network_graph, diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index ba9858be197..6734b76d018 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -2281,7 +2281,7 @@ fn fails_paying_invoice_with_unknown_required_features() { let created_at = alice.node.duration_since_epoch(); let invoice = invoice_request .verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).unwrap() - .respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at).unwrap() + .respond_using_derived_keys_no_std(&alice.node.flow.currency_conversion, payment_paths, payment_hash, created_at).unwrap() .features_unchecked(Bolt12InvoiceFeatures::unknown()) .build_and_sign(&secp_ctx).unwrap(); diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index ffc3ee4ae19..6c4eb9b0577 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -21,6 +21,8 @@ use crate::ln::channelmanager::{EventCompletionAction, HTLCSource, PaymentId}; use crate::ln::onion_utils; use crate::ln::onion_utils::{DecodedOnionFailure, HTLCFailReason}; use crate::offers::invoice::Bolt12Invoice; +#[cfg(async_payments)] +use crate::offers::invoice_request::CurrencyConversion; use crate::offers::invoice_request::InvoiceRequest; use crate::offers::nonce::Nonce; use crate::offers::static_invoice::StaticInvoice; @@ -1113,11 +1115,11 @@ impl OutboundPayments { #[cfg(async_payments)] #[rustfmt::skip] - pub(super) fn static_invoice_received( - &self, invoice: &StaticInvoice, payment_id: PaymentId, features: Bolt12InvoiceFeatures, + pub(super) fn static_invoice_received( + &self, invoice: &StaticInvoice, currency_conversion: &CC, payment_id: PaymentId, features: Bolt12InvoiceFeatures, best_block_height: u32, duration_since_epoch: Duration, entropy_source: ES, pending_events: &Mutex)>> - ) -> Result<(), Bolt12PaymentError> where ES::Target: EntropySource { + ) -> Result<(), Bolt12PaymentError> where ES::Target: EntropySource, CC::Target: CurrencyConversion { macro_rules! abandon_with_entry { ($payment: expr, $reason: expr) => { assert!( @@ -1154,7 +1156,7 @@ impl OutboundPayments { return Err(Bolt12PaymentError::SendingFailed(RetryableSendFailure::PaymentExpired)) } - let amount_msat = match InvoiceBuilder::::amount_msats(invreq) { + let amount_msat = match InvoiceBuilder::::amount_msats(invreq, currency_conversion) { Ok(amt) => amt, Err(_) => { // We check this during invoice request parsing, when constructing the invreq's @@ -2758,7 +2760,7 @@ mod tests { }; #[cfg(feature = "std")] use crate::offers::invoice::DEFAULT_RELATIVE_EXPIRY; - use crate::offers::invoice_request::InvoiceRequest; + use crate::offers::invoice_request::{DefaultCurrencyConversion, InvoiceRequest}; use crate::offers::nonce::Nonce; use crate::offers::offer::OfferBuilder; use crate::offers::test_utils::*; @@ -3141,7 +3143,7 @@ mod tests { .build().unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .build_and_sign().unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), created_at).unwrap() + .respond_with_no_std(&DefaultCurrencyConversion {}, payment_paths(), payment_hash(), created_at).unwrap() .build().unwrap() .sign(recipient_sign).unwrap(); @@ -3188,7 +3190,7 @@ mod tests { .build().unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .build_and_sign().unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() + .respond_with_no_std(&DefaultCurrencyConversion {}, payment_paths(), payment_hash(), now()).unwrap() .build().unwrap() .sign(recipient_sign).unwrap(); @@ -3251,7 +3253,7 @@ mod tests { .build().unwrap() .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap() .build_and_sign().unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() + .respond_with_no_std(&DefaultCurrencyConversion {}, payment_paths(), payment_hash(), now()).unwrap() .build().unwrap() .sign(recipient_sign).unwrap(); diff --git a/lightning/src/ln/peer_handler.rs b/lightning/src/ln/peer_handler.rs index 98e54eec925..60ca9518758 100644 --- a/lightning/src/ln/peer_handler.rs +++ b/lightning/src/ln/peer_handler.rs @@ -938,7 +938,7 @@ pub type SimpleRefPeerManager< 'a, 'b, 'c, 'd, 'e, 'f, 'logger, 'h, 'i, 'j, 'graph, 'k, 'mr, SD, M, T, F, C, L > = PeerManager< SD, - &'j SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'graph, 'logger, 'i, 'mr, M, T, F, L>, + &'j SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'graph, 'logger, 'i, 'mr, 'k, M, T, F, L>, &'f P2PGossipSync<&'graph NetworkGraph<&'logger L>, C, &'logger L>, &'h SimpleRefOnionMessenger<'a, 'b, 'c, 'd, 'e, 'graph, 'logger, 'i, 'j, 'k, M, T, F, L>, &'logger L, diff --git a/lightning/src/ln/reload_tests.rs b/lightning/src/ln/reload_tests.rs index 5682b3b44b1..691e23f166f 100644 --- a/lightning/src/ln/reload_tests.rs +++ b/lightning/src/ln/reload_tests.rs @@ -425,7 +425,7 @@ fn test_manager_serialize_deserialize_inconsistent_monitor() { let mut nodes_0_read = &nodes_0_serialized[..]; if let Err(msgs::DecodeError::DangerousValue) = - <(BlockHash, ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestMessageRouter, &test_utils::TestLogger>)>::read(&mut nodes_0_read, ChannelManagerReadArgs { + <(BlockHash, ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestMessageRouter, &test_utils::TestCurrencyConversion, &test_utils::TestLogger>)>::read(&mut nodes_0_read, ChannelManagerReadArgs { default_config: UserConfig::default(), entropy_source: keys_manager, node_signer: keys_manager, @@ -433,6 +433,7 @@ fn test_manager_serialize_deserialize_inconsistent_monitor() { fee_estimator: &fee_estimator, router: &nodes[0].router, message_router: &nodes[0].message_router, + currency_conversion: &nodes[0].currency_conversion, chain_monitor: nodes[0].chain_monitor, tx_broadcaster: nodes[0].tx_broadcaster, logger: &logger, @@ -443,7 +444,7 @@ fn test_manager_serialize_deserialize_inconsistent_monitor() { let mut nodes_0_read = &nodes_0_serialized[..]; let (_, nodes_0_deserialized_tmp) = - <(BlockHash, ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestMessageRouter, &test_utils::TestLogger>)>::read(&mut nodes_0_read, ChannelManagerReadArgs { + <(BlockHash, ChannelManager<&test_utils::TestChainMonitor, &test_utils::TestBroadcaster, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestKeysInterface, &test_utils::TestFeeEstimator, &test_utils::TestRouter, &test_utils::TestMessageRouter, &test_utils::TestCurrencyConversion, &test_utils::TestLogger>)>::read(&mut nodes_0_read, ChannelManagerReadArgs { default_config: UserConfig::default(), entropy_source: keys_manager, node_signer: keys_manager, @@ -451,6 +452,7 @@ fn test_manager_serialize_deserialize_inconsistent_monitor() { fee_estimator: &fee_estimator, router: nodes[0].router, message_router: &nodes[0].message_router, + currency_conversion: &nodes[0].currency_conversion, chain_monitor: nodes[0].chain_monitor, tx_broadcaster: nodes[0].tx_broadcaster, logger: &logger, diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index cd3f95e0841..920a358636f 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -41,7 +41,7 @@ use crate::offers::invoice::{ }; use crate::offers::invoice_error::InvoiceError; use crate::offers::invoice_request::{ - InvoiceRequest, InvoiceRequestBuilder, VerifiedInvoiceRequest, + CurrencyConversion, InvoiceRequest, InvoiceRequestBuilder, VerifiedInvoiceRequest, }; use crate::offers::nonce::Nonce; use crate::offers::offer::{DerivedMetadata, Offer, OfferBuilder}; @@ -81,9 +81,10 @@ use { /// /// [`OffersMessageFlow`] is parameterized by a [`MessageRouter`], which is responsible /// for finding message paths when initiating and retrying onion messages. -pub struct OffersMessageFlow +pub struct OffersMessageFlow where MR::Target: MessageRouter, + CC::Target: CurrencyConversion, { chain_hash: ChainHash, best_block: RwLock, @@ -97,6 +98,8 @@ where secp_ctx: Secp256k1, message_router: MR, + pub(crate) currency_conversion: CC, + #[cfg(not(any(test, feature = "_test_utils")))] pending_offers_messages: Mutex>, #[cfg(any(test, feature = "_test_utils"))] @@ -114,15 +117,17 @@ where pending_dns_onion_messages: Mutex>, } -impl OffersMessageFlow +impl OffersMessageFlow where MR::Target: MessageRouter, + CC::Target: CurrencyConversion, { /// Creates a new [`OffersMessageFlow`] pub fn new( chain_hash: ChainHash, best_block: BestBlock, our_network_pubkey: PublicKey, current_timestamp: u32, inbound_payment_key: inbound_payment::ExpandedKey, receive_auth_key: ReceiveAuthKey, secp_ctx: Secp256k1, message_router: MR, + currency_conversion: CC, ) -> Self { Self { chain_hash, @@ -137,6 +142,8 @@ where secp_ctx, message_router, + currency_conversion, + pending_offers_messages: Mutex::new(Vec::new()), pending_async_payments_messages: Mutex::new(Vec::new()), @@ -272,9 +279,10 @@ const DEFAULT_ASYNC_RECEIVE_OFFER_EXPIRY: Duration = Duration::from_secs(365 * 2 pub(crate) const TEST_DEFAULT_ASYNC_RECEIVE_OFFER_EXPIRY: Duration = DEFAULT_ASYNC_RECEIVE_OFFER_EXPIRY; -impl OffersMessageFlow +impl OffersMessageFlow where MR::Target: MessageRouter, + CC::Target: CurrencyConversion, { /// [`BlindedMessagePath`]s for an async recipient to communicate with this node and interactively /// build [`Offer`]s and [`StaticInvoice`]s for receiving async payments. @@ -422,9 +430,10 @@ pub enum InvreqResponseInstructions { }, } -impl OffersMessageFlow +impl OffersMessageFlow where MR::Target: MessageRouter, + CC::Target: CurrencyConversion, { /// Verifies an [`InvoiceRequest`] using the provided [`OffersContext`] or the [`InvoiceRequest::metadata`]. /// @@ -968,9 +977,14 @@ where let response = if invoice_request.keys.is_some() { #[cfg(feature = "std")] - let builder = invoice_request.respond_using_derived_keys(payment_paths, payment_hash); + let builder = invoice_request.respond_using_derived_keys( + &self.currency_conversion, + payment_paths, + payment_hash, + ); #[cfg(not(feature = "std"))] let builder = invoice_request.respond_using_derived_keys_no_std( + &self.currency_conversion, payment_paths, payment_hash, created_at, @@ -981,9 +995,14 @@ where .map_err(InvoiceError::from) } else { #[cfg(feature = "std")] - let builder = invoice_request.respond_with(payment_paths, payment_hash); + let builder = invoice_request.respond_with(&self.currency_conversion, payment_paths, payment_hash); #[cfg(not(feature = "std"))] - let builder = invoice_request.respond_with_no_std(payment_paths, payment_hash, created_at); + let builder = invoice_request.respond_with_no_std( + &self.currency_conversion, + payment_paths, + payment_hash, + created_at, + ); builder .map(InvoiceBuilder::::from) .and_then(|builder| builder.allow_mpp().build()) diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index 198e544fefc..40bd0c44993 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -24,7 +24,7 @@ //! use bitcoin::secp256k1::{Keypair, PublicKey, Secp256k1, SecretKey}; //! use core::convert::TryFrom; //! use lightning::offers::invoice::UnsignedBolt12Invoice; -//! use lightning::offers::invoice_request::InvoiceRequest; +//! use lightning::offers::invoice_request::{DefaultCurrencyConversion, InvoiceRequest}; //! use lightning::offers::refund::Refund; //! use lightning::util::ser::Writeable; //! @@ -50,13 +50,13 @@ #![cfg_attr( feature = "std", doc = " - .respond_with(payment_paths, payment_hash)? + .respond_with(&DefaultCurrencyConversion {}, payment_paths, payment_hash)? " )] #![cfg_attr( not(feature = "std"), doc = " - .respond_with_no_std(payment_paths, payment_hash, core::time::Duration::from_secs(0))? + .respond_with_no_std(&DefaultCurrencyConversion {}, payment_paths, payment_hash, core::time::Duration::from_secs(0))? " )] //! # ) @@ -125,10 +125,10 @@ use crate::ln::msgs::DecodeError; use crate::offers::invoice_macros::invoice_builder_methods_test_common; use crate::offers::invoice_macros::{invoice_accessors_common, invoice_builder_methods_common}; use crate::offers::invoice_request::{ - ExperimentalInvoiceRequestTlvStream, ExperimentalInvoiceRequestTlvStreamRef, InvoiceRequest, - InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, - EXPERIMENTAL_INVOICE_REQUEST_TYPES, INVOICE_REQUEST_PAYER_ID_TYPE, INVOICE_REQUEST_TYPES, - IV_BYTES as INVOICE_REQUEST_IV_BYTES, + CurrencyConversion, ExperimentalInvoiceRequestTlvStream, + ExperimentalInvoiceRequestTlvStreamRef, InvoiceRequest, InvoiceRequestContents, + InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef, EXPERIMENTAL_INVOICE_REQUEST_TYPES, + INVOICE_REQUEST_PAYER_ID_TYPE, INVOICE_REQUEST_TYPES, IV_BYTES as INVOICE_REQUEST_IV_BYTES, }; use crate::offers::merkle::{ self, SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, @@ -241,11 +241,15 @@ impl SigningPubkeyStrategy for DerivedSigningPubkey {} macro_rules! invoice_explicit_signing_pubkey_builder_methods { ($self: ident, $self_type: ty) => { #[cfg_attr(c_bindings, allow(dead_code))] - pub(super) fn for_offer( - invoice_request: &'a InvoiceRequest, payment_paths: Vec, - created_at: Duration, payment_hash: PaymentHash, signing_pubkey: PublicKey, - ) -> Result { - let amount_msats = Self::amount_msats(invoice_request)?; + pub(super) fn for_offer( + invoice_request: &'a InvoiceRequest, currency_conversion: &CC, + payment_paths: Vec, created_at: Duration, + payment_hash: PaymentHash, signing_pubkey: PublicKey, + ) -> Result + where + CC::Target: $crate::offers::invoice_request::CurrencyConversion, + { + let amount_msats = Self::amount_msats(invoice_request, currency_conversion)?; let contents = InvoiceContents::ForOffer { invoice_request: invoice_request.contents.clone(), fields: Self::fields( @@ -313,11 +317,15 @@ macro_rules! invoice_explicit_signing_pubkey_builder_methods { macro_rules! invoice_derived_signing_pubkey_builder_methods { ($self: ident, $self_type: ty) => { #[cfg_attr(c_bindings, allow(dead_code))] - pub(super) fn for_offer_using_keys( - invoice_request: &'a InvoiceRequest, payment_paths: Vec, - created_at: Duration, payment_hash: PaymentHash, keys: Keypair, - ) -> Result { - let amount_msats = Self::amount_msats(invoice_request)?; + pub(super) fn for_offer_using_keys( + invoice_request: &'a InvoiceRequest, currency_conversion: &CC, + payment_paths: Vec, created_at: Duration, + payment_hash: PaymentHash, keys: Keypair, + ) -> Result + where + CC::Target: $crate::offers::invoice_request::CurrencyConversion, + { + let amount_msats = Self::amount_msats(invoice_request, currency_conversion)?; let signing_pubkey = keys.public_key(); let contents = InvoiceContents::ForOffer { invoice_request: invoice_request.contents.clone(), @@ -393,18 +401,28 @@ macro_rules! invoice_builder_methods { ( $self: ident, $self_type: ty, $return_type: ty, $return_value: expr, $type_param: ty $(, $self_mut: tt)? ) => { - pub(crate) fn amount_msats( - invoice_request: &InvoiceRequest, - ) -> Result { - match invoice_request.contents.inner.amount_msats() { - Some(amount_msats) => Ok(amount_msats), - None => match invoice_request.contents.inner.offer.amount() { - Some(Amount::Bitcoin { amount_msats }) => amount_msats - .checked_mul(invoice_request.quantity().unwrap_or(1)) - .ok_or(Bolt12SemanticError::InvalidAmount), - Some(Amount::Currency { .. }) => Err(Bolt12SemanticError::UnsupportedCurrency), - None => Err(Bolt12SemanticError::MissingAmount), + pub(crate) fn amount_msats( + invoice_request: &InvoiceRequest, currency_conversion: &CC, + ) -> Result + where + CC::Target: $crate::offers::invoice_request::CurrencyConversion, + { + // Case 1: InvoiceRequest has a direct msats amount + if let Some(amount_msats) = invoice_request.contents.inner.amount_msats() { + return Ok(amount_msats); + } + + // Case 2: Use offer amount and quantity to compute final msats + let quantity = invoice_request.quantity().unwrap_or(1); + match invoice_request.contents.inner.offer.amount() { + Some(Amount::Bitcoin { amount_msats }) => { + amount_msats.checked_mul(quantity).ok_or(Bolt12SemanticError::InvalidAmount) + }, + Some(Amount::Currency { iso4217_code, amount }) => { + let unit_msats = currency_conversion.fiat_to_msats(iso4217_code)? * amount; + unit_msats.checked_mul(quantity).ok_or(Bolt12SemanticError::InvalidAmount) }, + None => Err(Bolt12SemanticError::MissingAmount), } } @@ -1818,7 +1836,8 @@ mod tests { use crate::ln::inbound_payment::ExpandedKey; use crate::ln::msgs::DecodeError; use crate::offers::invoice_request::{ - ExperimentalInvoiceRequestTlvStreamRef, InvoiceRequestTlvStreamRef, + DefaultCurrencyConversion, ExperimentalInvoiceRequestTlvStreamRef, + InvoiceRequestTlvStreamRef, }; use crate::offers::merkle::{self, SignError, SignatureTlvStreamRef, TaggedHash, TlvStream}; use crate::offers::nonce::Nonce; @@ -1876,7 +1895,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths.clone(), payment_hash, now) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths.clone(), + payment_hash, + now, + ) .unwrap() .build() .unwrap(); @@ -2147,7 +2171,7 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with(payment_paths(), payment_hash()) + .respond_with(&DefaultCurrencyConversion {}, payment_paths(), payment_hash()) .unwrap() .build() { @@ -2162,7 +2186,7 @@ mod tests { .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id) .unwrap() .build_unchecked_and_sign() - .respond_with(payment_paths(), payment_hash()) + .respond_with(&DefaultCurrencyConversion {}, payment_paths(), payment_hash()) .unwrap() .build() { @@ -2239,7 +2263,12 @@ mod tests { .clone() .verify_using_recipient_data(nonce, &expanded_key, &secp_ctx) .unwrap() - .respond_using_derived_keys_no_std(payment_paths(), payment_hash(), now()) + .respond_using_derived_keys_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .build_and_sign(&secp_ctx) { @@ -2266,8 +2295,12 @@ mod tests { match invoice_request .verify_using_metadata(&expanded_key, &secp_ctx) .unwrap() - .respond_using_derived_keys_no_std(payment_paths(), payment_hash(), now()) - { + .respond_using_derived_keys_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) { Ok(_) => panic!("expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidMetadata), } @@ -2356,7 +2389,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now, + ) .unwrap() .relative_expiry(one_hour.as_secs() as u32) .build() @@ -2377,7 +2415,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now - one_hour) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now - one_hour, + ) .unwrap() .relative_expiry(one_hour.as_secs() as u32 - 1) .build() @@ -2409,7 +2452,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .build() .unwrap() @@ -2439,7 +2487,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .build() .unwrap() @@ -2459,8 +2512,12 @@ mod tests { .quantity(u64::max_value()) .unwrap() .build_unchecked_and_sign() - .respond_with_no_std(payment_paths(), payment_hash(), now()) - { + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) { Ok(_) => panic!("expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidAmount), } @@ -2487,7 +2544,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .fallback_v0_p2wsh(&script.wscript_hash()) .fallback_v0_p2wpkh(&pubkey.wpubkey_hash().unwrap()) @@ -2543,7 +2605,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .allow_mpp() .build() @@ -2571,7 +2638,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .build() .unwrap() @@ -2589,7 +2661,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .build() .unwrap() @@ -2616,7 +2693,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .build() .unwrap() @@ -2693,7 +2775,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .build() .unwrap() @@ -2737,7 +2824,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .relative_expiry(3600) .build() @@ -2770,7 +2862,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .build() .unwrap() @@ -2814,7 +2911,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .build() .unwrap() @@ -2856,7 +2958,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .allow_mpp() .build() @@ -2899,11 +3006,23 @@ mod tests { .build_and_sign() .unwrap(); #[cfg(not(c_bindings))] - let invoice_builder = - invoice_request.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap(); + let invoice_builder = invoice_request + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) + .unwrap(); #[cfg(c_bindings)] - let mut invoice_builder = - invoice_request.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap(); + let mut invoice_builder = invoice_request + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) + .unwrap(); let invoice_builder = invoice_builder .fallback_v0_p2wsh(&script.wscript_hash()) .fallback_v0_p2wpkh(&pubkey.wpubkey_hash().unwrap()) @@ -2962,7 +3081,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .build() .unwrap() @@ -3050,6 +3174,7 @@ mod tests { .build_and_sign() .unwrap() .respond_with_no_std_using_signing_pubkey( + &DefaultCurrencyConversion {}, payment_paths(), payment_hash(), now(), @@ -3080,6 +3205,7 @@ mod tests { .build_and_sign() .unwrap() .respond_with_no_std_using_signing_pubkey( + &DefaultCurrencyConversion {}, payment_paths(), payment_hash(), now(), @@ -3121,7 +3247,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .amount_msats_unchecked(2000) .build() @@ -3150,7 +3281,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .amount_msats_unchecked(2000) .build() @@ -3214,7 +3350,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .build() .unwrap() @@ -3247,7 +3388,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .build() .unwrap() @@ -3290,7 +3436,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .build() .unwrap(); @@ -3329,7 +3480,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .build() .unwrap(); @@ -3375,7 +3531,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .experimental_baz(42) .build() @@ -3401,7 +3562,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .build() .unwrap(); @@ -3442,7 +3608,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .build() .unwrap(); @@ -3480,7 +3651,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .build() .unwrap() @@ -3521,7 +3697,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .build() .unwrap() @@ -3556,7 +3737,12 @@ mod tests { .unwrap() .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .build() .unwrap() @@ -3604,7 +3790,12 @@ mod tests { .unwrap(); let invoice = invoice_request - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .build() .unwrap() @@ -3650,7 +3841,7 @@ mod tests { .unwrap(); let invoice = invoice_request - .respond_with_no_std(payment_paths, payment_hash(), now) + .respond_with_no_std(&DefaultCurrencyConversion {}, payment_paths, payment_hash(), now) .unwrap() .build() .unwrap() diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index dedbb27c674..bd1b7da7e22 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -76,8 +76,9 @@ use crate::offers::merkle::{ }; use crate::offers::nonce::Nonce; use crate::offers::offer::{ - Amount, ExperimentalOfferTlvStream, ExperimentalOfferTlvStreamRef, Offer, OfferContents, - OfferId, OfferTlvStream, OfferTlvStreamRef, EXPERIMENTAL_OFFER_TYPES, OFFER_TYPES, + Amount, CurrencyCode, ExperimentalOfferTlvStream, ExperimentalOfferTlvStreamRef, Offer, + OfferContents, OfferId, OfferTlvStream, OfferTlvStreamRef, EXPERIMENTAL_OFFER_TYPES, + OFFER_TYPES, }; use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage}; use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef}; @@ -94,6 +95,7 @@ use bitcoin::constants::ChainHash; use bitcoin::network::Network; use bitcoin::secp256k1::schnorr::Signature; use bitcoin::secp256k1::{self, Keypair, PublicKey, Secp256k1}; +use core::ops::Deref; #[cfg(not(c_bindings))] use crate::offers::invoice::{DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder}; @@ -573,6 +575,35 @@ impl AsRef for UnsignedInvoiceRequest { } } +/// A trait for converting fiat currencies into millisatoshi values. +/// +/// This is used for handling conversions between fiat currencies and Bitcoin denominated in millisatoshis +/// when working with Bolt12 invoice requests. +/// +/// Implementors must provide a method to convert from a specified fiat currency (using ISO 4217 currency codes) +/// to millisatoshis, handling any potential conversion errors. +pub trait CurrencyConversion { + /// Converts a fiat currency specified by its ISO 4217 code to millisatoshis. + fn fiat_to_msats(&self, iso4217_code: CurrencyCode) -> Result; +} + +/// A default implementation of the `CurrencyConversion` trait that does not support any currency conversions. +pub struct DefaultCurrencyConversion {} + +impl Deref for DefaultCurrencyConversion { + type Target = Self; + + fn deref(&self) -> &Self::Target { + self + } +} + +impl CurrencyConversion for DefaultCurrencyConversion { + fn fiat_to_msats(&self, _iso4217_code: CurrencyCode) -> Result { + Err(Bolt12SemanticError::UnsupportedCurrency) + } +} + /// An `InvoiceRequest` is a request for a [`Bolt12Invoice`] formulated from an [`Offer`]. /// /// An offer may provide choices such as quantity, amount, chain, features, etc. An invoice request @@ -715,14 +746,17 @@ macro_rules! invoice_request_respond_with_explicit_signing_pubkey_methods { ( /// /// [`Duration`]: core::time::Duration #[cfg(feature = "std")] - pub fn respond_with( - &$self, payment_paths: Vec, payment_hash: PaymentHash - ) -> Result<$builder, Bolt12SemanticError> { + pub fn respond_with( + &$self, currency_conversion: &CC, payment_paths: Vec, payment_hash: PaymentHash + ) -> Result<$builder, Bolt12SemanticError> + where + CC::Target: CurrencyConversion + { let created_at = std::time::SystemTime::now() .duration_since(std::time::SystemTime::UNIX_EPOCH) .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); - $contents.respond_with_no_std(payment_paths, payment_hash, created_at) + $contents.respond_with_no_std(currency_conversion, payment_paths, payment_hash, created_at) } /// Creates an [`InvoiceBuilder`] for the request with the given required fields. @@ -750,10 +784,13 @@ macro_rules! invoice_request_respond_with_explicit_signing_pubkey_methods { ( /// /// [`Bolt12Invoice::created_at`]: crate::offers::invoice::Bolt12Invoice::created_at /// [`OfferBuilder::deriving_signing_pubkey`]: crate::offers::offer::OfferBuilder::deriving_signing_pubkey - pub fn respond_with_no_std( - &$self, payment_paths: Vec, payment_hash: PaymentHash, + pub fn respond_with_no_std( + &$self, currency_conversion: &CC, payment_paths: Vec, payment_hash: PaymentHash, created_at: core::time::Duration - ) -> Result<$builder, Bolt12SemanticError> { + ) -> Result<$builder, Bolt12SemanticError> + where + CC::Target: CurrencyConversion + { if $contents.invoice_request_features().requires_unknown_bits() { return Err(Bolt12SemanticError::UnknownRequiredFeatures); } @@ -763,22 +800,25 @@ macro_rules! invoice_request_respond_with_explicit_signing_pubkey_methods { ( None => return Err(Bolt12SemanticError::MissingIssuerSigningPubkey), }; - <$builder>::for_offer(&$contents, payment_paths, created_at, payment_hash, signing_pubkey) + <$builder>::for_offer(&$contents, currency_conversion, payment_paths, created_at, payment_hash, signing_pubkey) } #[cfg(test)] #[allow(dead_code)] - pub(super) fn respond_with_no_std_using_signing_pubkey( - &$self, payment_paths: Vec, payment_hash: PaymentHash, + pub(super) fn respond_with_no_std_using_signing_pubkey( + &$self, currency_conversion: &CC, payment_paths: Vec, payment_hash: PaymentHash, created_at: core::time::Duration, signing_pubkey: PublicKey - ) -> Result<$builder, Bolt12SemanticError> { + ) -> Result<$builder, Bolt12SemanticError> + where + CC::Target: CurrencyConversion + { debug_assert!($contents.contents.inner.offer.issuer_signing_pubkey().is_none()); if $contents.invoice_request_features().requires_unknown_bits() { return Err(Bolt12SemanticError::UnknownRequiredFeatures); } - <$builder>::for_offer(&$contents, payment_paths, created_at, payment_hash, signing_pubkey) + <$builder>::for_offer(&$contents, currency_conversion, payment_paths, created_at, payment_hash, signing_pubkey) } } } @@ -920,14 +960,17 @@ macro_rules! invoice_request_respond_with_derived_signing_pubkey_methods { ( /// /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice #[cfg(feature = "std")] - pub fn respond_using_derived_keys( - &$self, payment_paths: Vec, payment_hash: PaymentHash - ) -> Result<$builder, Bolt12SemanticError> { + pub fn respond_using_derived_keys( + &$self, currency_conversion: &CC, payment_paths: Vec, payment_hash: PaymentHash + ) -> Result<$builder, Bolt12SemanticError> + where + CC::Target: CurrencyConversion + { let created_at = std::time::SystemTime::now() .duration_since(std::time::SystemTime::UNIX_EPOCH) .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH"); - $self.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at) + $self.respond_using_derived_keys_no_std(currency_conversion, payment_paths, payment_hash, created_at) } /// Creates an [`InvoiceBuilder`] for the request using the given required fields and that uses @@ -937,10 +980,13 @@ macro_rules! invoice_request_respond_with_derived_signing_pubkey_methods { ( /// See [`InvoiceRequest::respond_with_no_std`] for further details. /// /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice - pub fn respond_using_derived_keys_no_std( - &$self, payment_paths: Vec, payment_hash: PaymentHash, + pub fn respond_using_derived_keys_no_std( + &$self, currency_conversion: &CC, payment_paths: Vec, payment_hash: PaymentHash, created_at: core::time::Duration - ) -> Result<$builder, Bolt12SemanticError> { + ) -> Result<$builder, Bolt12SemanticError> + where + CC::Target: CurrencyConversion + { if $self.inner.invoice_request_features().requires_unknown_bits() { return Err(Bolt12SemanticError::UnknownRequiredFeatures); } @@ -956,7 +1002,7 @@ macro_rules! invoice_request_respond_with_derived_signing_pubkey_methods { ( } <$builder>::for_offer_using_keys( - &$self.inner, payment_paths, created_at, payment_hash, keys + &$self.inner, currency_conversion, payment_paths, created_at, payment_hash, keys ) } } } @@ -1460,7 +1506,7 @@ mod tests { use crate::ln::inbound_payment::ExpandedKey; use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT}; use crate::offers::invoice::{Bolt12Invoice, SIGNATURE_TAG as INVOICE_SIGNATURE_TAG}; - use crate::offers::invoice_request::string_truncate_safe; + use crate::offers::invoice_request::{string_truncate_safe, DefaultCurrencyConversion}; use crate::offers::merkle::{self, SignatureTlvStreamRef, TaggedHash, TlvStream}; use crate::offers::nonce::Nonce; #[cfg(not(c_bindings))] @@ -1631,7 +1677,12 @@ mod tests { .unwrap(); let invoice = invoice_request - .respond_with_no_std(payment_paths(), payment_hash(), now()) + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) .unwrap() .experimental_baz(42) .build() @@ -2215,8 +2266,12 @@ mod tests { .features_unchecked(InvoiceRequestFeatures::unknown()) .build_and_sign() .unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()) - { + .respond_with_no_std( + &DefaultCurrencyConversion {}, + payment_paths(), + payment_hash(), + now(), + ) { Ok(_) => panic!("expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::UnknownRequiredFeatures), } diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 38c8cd304d9..b2b7802469e 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -2328,11 +2328,11 @@ pub type SimpleRefOnionMessenger<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, M, T, F &'a KeysManager, &'a KeysManager, &'b L, - &'j SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, M, T, F, L>, + &'j SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, M, T, F, L>, &'i DefaultMessageRouter<&'g NetworkGraph<&'b L>, &'b L, &'a KeysManager>, - &'j SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, M, T, F, L>, - &'j SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, M, T, F, L>, - &'j SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, M, T, F, L>, + &'j SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, M, T, F, L>, + &'j SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, M, T, F, L>, + &'j SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, M, T, F, L>, IgnoringMessageHandler, >; @@ -2350,10 +2350,10 @@ pub type SimpleRefOnionMessenger<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, M, T, F &'a KeysManager, &'a KeysManager, &'b L, - &'j SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, M, T, F, L>, + &'j SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, M, T, F, L>, &'i DefaultMessageRouter<&'g NetworkGraph<&'b L>, &'b L, &'a KeysManager>, - &'j SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, M, T, F, L>, - &'j SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, M, T, F, L>, + &'j SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, M, T, F, L>, + &'j SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, M, T, F, L>, IgnoringMessageHandler, IgnoringMessageHandler, >; diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index e5388b76049..eec6d09e642 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -33,6 +33,9 @@ use crate::ln::script::ShutdownScript; use crate::ln::types::ChannelId; use crate::ln::{msgs, wire}; use crate::offers::invoice::UnsignedBolt12Invoice; +use crate::offers::invoice_request::{CurrencyConversion, DefaultCurrencyConversion}; +use crate::offers::offer::CurrencyCode; +use crate::offers::parse::Bolt12SemanticError; use crate::onion_message::messenger::{ DefaultMessageRouter, Destination, MessageRouter, NodeIdMessageRouter, OnionMessagePath, }; @@ -412,6 +415,20 @@ impl<'a> MessageRouter for TestMessageRouter<'a> { } } +pub struct TestCurrencyConversion(DefaultCurrencyConversion); + +impl TestCurrencyConversion { + pub fn new() -> Self { + TestCurrencyConversion(DefaultCurrencyConversion {}) + } +} + +impl CurrencyConversion for TestCurrencyConversion { + fn fiat_to_msats(&self, _iso4217_code: CurrencyCode) -> Result { + Err(Bolt12SemanticError::UnsupportedCurrency) + } +} + pub struct OnlyReadsKeysInterface {} impl EntropySource for OnlyReadsKeysInterface {