Merge pull request #1567 from tnull/2022-06-send-probe
authorJeffrey Czyz <jkczyz@gmail.com>
Thu, 7 Jul 2022 14:27:14 +0000 (09:27 -0500)
committerGitHub <noreply@github.com>
Thu, 7 Jul 2022 14:27:14 +0000 (09:27 -0500)
Add simple probing API

1  2 
lightning/src/ln/channelmanager.rs

index 36c14242d9edffab80f07c864b0a35f5b8693fa5,29d9310ba33e61ec664c8160e30833f2e2edfbf6..7db5e3860bf29f5b0f981132fdfd7e69873d3c47
@@@ -740,6 -740,11 +740,11 @@@ pub struct ChannelManager<Signer: Sign
        /// [fake scids]: crate::util::scid_utils::fake_scid
        fake_scid_rand_bytes: [u8; 32],
  
+       /// When we send payment probes, we generate the [`PaymentHash`] based on this cookie secret
+       /// and a random [`PaymentId`]. This allows us to discern probes from real payments, without
+       /// keeping additional state.
+       probing_cookie_secret: [u8; 32],
        /// Used to track the last value sent in a node_announcement "timestamp" field. We ensure this
        /// value increases strictly since we don't assume access to a time source.
        last_node_announcement_serial: AtomicUsize,
@@@ -1589,6 -1594,8 +1594,8 @@@ impl<Signer: Sign, M: Deref, T: Deref, 
                        inbound_payment_key: expanded_inbound_key,
                        fake_scid_rand_bytes: keys_manager.get_secure_random_bytes(),
  
+                       probing_cookie_secret: keys_manager.get_secure_random_bytes(),
                        last_node_announcement_serial: AtomicUsize::new(0),
                        highest_seen_timestamp: AtomicUsize::new(0),
  
                }
        }
  
+       /// Send a payment that is probing the given route for liquidity. We calculate the
+       /// [`PaymentHash`] of probes based on a static secret and a random [`PaymentId`], which allows
+       /// us to easily discern them from real payments.
+       pub fn send_probe(&self, hops: Vec<RouteHop>) -> Result<(PaymentHash, PaymentId), PaymentSendFailure> {
+               let payment_id = PaymentId(self.keys_manager.get_secure_random_bytes());
+               let payment_hash = self.probing_cookie_from_id(&payment_id);
+               if hops.len() < 2 {
+                       return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError {
+                               err: "No need probing a path with less than two hops".to_string()
+                       }))
+               }
+               let route = Route { paths: vec![hops], payment_params: None };
+               match self.send_payment_internal(&route, payment_hash, &None, None, Some(payment_id), None) {
+                       Ok(payment_id) => Ok((payment_hash, payment_id)),
+                       Err(e) => Err(e)
+               }
+       }
+       /// Returns whether a payment with the given [`PaymentHash`] and [`PaymentId`] is, in fact, a
+       /// payment probe.
+       pub(crate) fn payment_is_probe(&self, payment_hash: &PaymentHash, payment_id: &PaymentId) -> bool {
+               let target_payment_hash = self.probing_cookie_from_id(payment_id);
+               target_payment_hash == *payment_hash
+       }
+       /// Returns the 'probing cookie' for the given [`PaymentId`].
+       fn probing_cookie_from_id(&self, payment_id: &PaymentId) -> PaymentHash {
+               let mut preimage = [0u8; 64];
+               preimage[..32].copy_from_slice(&self.probing_cookie_secret);
+               preimage[32..].copy_from_slice(&payment_id.0);
+               PaymentHash(Sha256::hash(&preimage).into_inner())
+       }
        /// Handles the generation of a funding transaction, optionally (for tests) with a function
        /// which checks the correctness of the funding transaction given the associated channel.
        fn funding_transaction_generated_intern<FundingOutput: Fn(&Channel<Signer>, &Transaction) -> Result<OutPoint, APIError>>(
  
        #[allow(dead_code)]
        // Messages of up to 64KB should never end up more than half full with addresses, as that would
 -      // be absurd. We ensure this by checking that at least 500 (our stated public contract on when
 +      // be absurd. We ensure this by checking that at least 100 (our stated public contract on when
        // broadcast_node_announcement panics) of the maximum-length addresses would fit in a 64KB
        // message...
        const HALF_MESSAGE_IS_ADDRS: u32 = ::core::u16::MAX as u32 / (NetAddress::MAX_LEN as u32 + 1) / 2;
        #[deny(const_err)]
        #[allow(dead_code)]
        // ...by failing to compile if the number of addresses that would be half of a message is
 -      // smaller than 500:
 -      const STATIC_ASSERT: u32 = Self::HALF_MESSAGE_IS_ADDRS - 500;
 +      // smaller than 100:
 +      const STATIC_ASSERT: u32 = Self::HALF_MESSAGE_IS_ADDRS - 100;
  
        /// Regenerates channel_announcements and generates a signed node_announcement from the given
        /// arguments, providing them in corresponding events via
        /// tying these addresses together and to this node. If you wish to preserve user privacy,
        /// addresses should likely contain only Tor Onion addresses.
        ///
 -      /// Panics if `addresses` is absurdly large (more than 500).
 +      /// Panics if `addresses` is absurdly large (more than 100).
        ///
        /// [`get_and_clear_pending_msg_events`]: MessageSendEventsProvider::get_and_clear_pending_msg_events
        pub fn broadcast_node_announcement(&self, rgb: [u8; 3], alias: [u8; 32], mut addresses: Vec<NetAddress>) {
                let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);
  
 -              if addresses.len() > 500 {
 +              if addresses.len() > 100 {
                        panic!("More than half the message size was taken up by public addresses!");
                }
  
                                                let (network_update, short_channel_id, payment_retryable, onion_error_code, onion_error_data) = onion_utils::process_onion_failure(&self.secp_ctx, &self.logger, &source, err.data.clone());
  #[cfg(not(test))]
                                                let (network_update, short_channel_id, payment_retryable, _, _) = onion_utils::process_onion_failure(&self.secp_ctx, &self.logger, &source, err.data.clone());
-                                               // TODO: If we decided to blame ourselves (or one of our channels) in
-                                               // process_onion_failure we should close that channel as it implies our
-                                               // next-hop is needlessly blaming us!
-                                               events::Event::PaymentPathFailed {
-                                                       payment_id: Some(payment_id),
-                                                       payment_hash: payment_hash.clone(),
-                                                       rejected_by_dest: !payment_retryable,
-                                                       network_update,
-                                                       all_paths_failed,
-                                                       path: path.clone(),
-                                                       short_channel_id,
-                                                       retry,
- #[cfg(test)]
-                                                       error_code: onion_error_code,
- #[cfg(test)]
-                                                       error_data: onion_error_data
+                                               if self.payment_is_probe(payment_hash, &payment_id) {
+                                                       if !payment_retryable {
+                                                               events::Event::ProbeSuccessful {
+                                                                       payment_id,
+                                                                       payment_hash: payment_hash.clone(),
+                                                                       path: path.clone(),
+                                                               }
+                                                       } else {
+                                                               events::Event::ProbeFailed {
+                                                                       payment_id: payment_id,
+                                                                       payment_hash: payment_hash.clone(),
+                                                                       path: path.clone(),
+                                                                       short_channel_id,
+                                                               }
+                                                       }
+                                               } else {
+                                                       // TODO: If we decided to blame ourselves (or one of our channels) in
+                                                       // process_onion_failure we should close that channel as it implies our
+                                                       // next-hop is needlessly blaming us!
+                                                       events::Event::PaymentPathFailed {
+                                                               payment_id: Some(payment_id),
+                                                               payment_hash: payment_hash.clone(),
+                                                               rejected_by_dest: !payment_retryable,
+                                                               network_update,
+                                                               all_paths_failed,
+                                                               path: path.clone(),
+                                                               short_channel_id,
+                                                               retry,
+                                                               #[cfg(test)]
+                                                               error_code: onion_error_code,
+                                                               #[cfg(test)]
+                                                               error_data: onion_error_data
+                                                       }
                                                }
                                        },
                                        &HTLCFailReason::Reason {
@@@ -6198,7 -6260,7 +6260,7 @@@ impl_writeable_tlv_based!(ChannelDetail
        (18, outbound_capacity_msat, required),
        // Note that by the time we get past the required read above, outbound_capacity_msat will be
        // filled in, so we can safely unwrap it here.
 -      (19, next_outbound_htlc_limit_msat, (default_value, outbound_capacity_msat.0.unwrap())),
 +      (19, next_outbound_htlc_limit_msat, (default_value, outbound_capacity_msat.0.unwrap() as u64)),
        (20, inbound_capacity_msat, required),
        (22, confirmations_required, option),
        (24, force_close_spend_delay, option),
@@@ -6631,6 -6693,7 +6693,7 @@@ impl<Signer: Sign, M: Deref, T: Deref, 
                        (5, self.our_network_pubkey, required),
                        (7, self.fake_scid_rand_bytes, required),
                        (9, htlc_purposes, vec_type),
+                       (11, self.probing_cookie_secret, required),
                });
  
                Ok(())
@@@ -6927,6 -6990,7 +6990,7 @@@ impl<'a, Signer: Sign, M: Deref, T: Der
                let mut pending_outbound_payments = None;
                let mut received_network_pubkey: Option<PublicKey> = None;
                let mut fake_scid_rand_bytes: Option<[u8; 32]> = None;
+               let mut probing_cookie_secret: Option<[u8; 32]> = None;
                let mut claimable_htlc_purposes = None;
                read_tlv_fields!(reader, {
                        (1, pending_outbound_payments_no_retry, option),
                        (5, received_network_pubkey, option),
                        (7, fake_scid_rand_bytes, option),
                        (9, claimable_htlc_purposes, vec_type),
+                       (11, probing_cookie_secret, option),
                });
                if fake_scid_rand_bytes.is_none() {
                        fake_scid_rand_bytes = Some(args.keys_manager.get_secure_random_bytes());
                }
  
+               if probing_cookie_secret.is_none() {
+                       probing_cookie_secret = Some(args.keys_manager.get_secure_random_bytes());
+               }
                if pending_outbound_payments.is_none() && pending_outbound_payments_no_retry.is_none() {
                        pending_outbound_payments = Some(pending_outbound_payments_compat);
                } else if pending_outbound_payments.is_none() {
                        outbound_scid_aliases: Mutex::new(outbound_scid_aliases),
                        fake_scid_rand_bytes: fake_scid_rand_bytes.unwrap(),
  
+                       probing_cookie_secret: probing_cookie_secret.unwrap(),
                        our_network_key,
                        our_network_pubkey,
                        secp_ctx,