Skip to content

Add support for phantom node payments #1199

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Feb 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions fuzz/src/chanmon_consistency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use lightning::chain::{BestBlock, ChannelMonitorUpdateErr, chainmonitor, channel
use lightning::chain::channelmonitor::{ChannelMonitor, MonitorEvent};
use lightning::chain::transaction::OutPoint;
use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
use lightning::chain::keysinterface::{KeyMaterial, KeysInterface, InMemorySigner};
use lightning::chain::keysinterface::{KeyMaterial, KeysInterface, InMemorySigner, Recipient};
use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
use lightning::ln::channelmanager::{ChainParameters, ChannelManager, PaymentSendFailure, ChannelManagerReadArgs};
use lightning::ln::channel::FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE;
Expand Down Expand Up @@ -161,8 +161,8 @@ struct KeyProvider {
impl KeysInterface for KeyProvider {
type Signer = EnforcingSigner;

fn get_node_secret(&self) -> SecretKey {
SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, self.node_id]).unwrap()
fn get_node_secret(&self, _recipient: Recipient) -> Result<SecretKey, ()> {
Ok(SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, self.node_id]).unwrap())
}

fn get_inbound_payment_key_material(&self) -> KeyMaterial {
Expand All @@ -188,7 +188,7 @@ impl KeysInterface for KeyProvider {
let id = self.rand_bytes_id.fetch_add(1, atomic::Ordering::Relaxed);
let keys = InMemorySigner::new(
&secp_ctx,
self.get_node_secret(),
self.get_node_secret(Recipient::Node).unwrap(),
SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, self.node_id]).unwrap(),
SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, self.node_id]).unwrap(),
SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, self.node_id]).unwrap(),
Expand All @@ -212,7 +212,7 @@ impl KeysInterface for KeyProvider {
fn read_chan_signer(&self, buffer: &[u8]) -> Result<Self::Signer, DecodeError> {
let mut reader = std::io::Cursor::new(buffer);

let inner: InMemorySigner = ReadableArgs::read(&mut reader, self.get_node_secret())?;
let inner: InMemorySigner = ReadableArgs::read(&mut reader, self.get_node_secret(Recipient::Node).unwrap())?;
let state = self.make_enforcement_state_cell(inner.commitment_seed);

Ok(EnforcingSigner {
Expand All @@ -222,7 +222,7 @@ impl KeysInterface for KeyProvider {
})
}

fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5]) -> Result<RecoverableSignature, ()> {
fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient) -> Result<RecoverableSignature, ()> {
unreachable!()
}
}
Expand Down
11 changes: 6 additions & 5 deletions fuzz/src/full_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use lightning::chain::{BestBlock, Confirm, Listen};
use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
use lightning::chain::chainmonitor;
use lightning::chain::transaction::OutPoint;
use lightning::chain::keysinterface::{InMemorySigner, KeyMaterial, KeysInterface};
use lightning::chain::keysinterface::{InMemorySigner, Recipient, KeyMaterial, KeysInterface};
use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
use lightning::ln::channelmanager::{ChainParameters, ChannelManager};
use lightning::ln::peer_handler::{MessageHandler,PeerManager,SocketDescriptor,IgnoringMessageHandler};
Expand Down Expand Up @@ -265,8 +265,8 @@ struct KeyProvider {
impl KeysInterface for KeyProvider {
type Signer = EnforcingSigner;

fn get_node_secret(&self) -> SecretKey {
self.node_secret.clone()
fn get_node_secret(&self, _recipient: Recipient) -> Result<SecretKey, ()> {
Ok(self.node_secret.clone())
}

fn get_inbound_payment_key_material(&self) -> KeyMaterial {
Expand Down Expand Up @@ -336,7 +336,7 @@ impl KeysInterface for KeyProvider {
))
}

fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5]) -> Result<RecoverableSignature, ()> {
fn sign_invoice(&self, _hrp_bytes: &[u8], _invoice_data: &[u5], _recipient: Recipient) -> Result<RecoverableSignature, ()> {
unreachable!()
}
}
Expand Down Expand Up @@ -390,7 +390,8 @@ pub fn do_test(data: &[u8], logger: &Arc<dyn Logger>) {
best_block: BestBlock::from_genesis(network),
};
let channelmanager = Arc::new(ChannelManager::new(fee_est.clone(), monitor.clone(), broadcast.clone(), Arc::clone(&logger), keys_manager.clone(), config, params));
let our_id = PublicKey::from_secret_key(&Secp256k1::signing_only(), &keys_manager.get_node_secret());
keys_manager.counter.fetch_sub(1, Ordering::AcqRel);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A comment here would be useful for posterity.

let our_id = PublicKey::from_secret_key(&Secp256k1::signing_only(), &keys_manager.get_node_secret(Recipient::Node).unwrap());
let network_graph = Arc::new(NetworkGraph::new(genesis_block(network).block_hash()));
let net_graph_msg_handler = Arc::new(NetGraphMsgHandler::new(Arc::clone(&network_graph), None, Arc::clone(&logger)));
let scorer = FixedPenaltyScorer::with_penalty(0);
Expand Down
4 changes: 2 additions & 2 deletions lightning-background-processor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ mod tests {
use bitcoin::network::constants::Network;
use lightning::chain::{BestBlock, Confirm, chainmonitor};
use lightning::chain::channelmonitor::ANTI_REORG_DELAY;
use lightning::chain::keysinterface::{InMemorySigner, KeysInterface, KeysManager};
use lightning::chain::keysinterface::{InMemorySigner, Recipient, KeysInterface, KeysManager};
use lightning::chain::transaction::OutPoint;
use lightning::get_event_msg;
use lightning::ln::channelmanager::{BREAKDOWN_TIMEOUT, ChainParameters, ChannelManager, SimpleArcChannelManager};
Expand Down Expand Up @@ -426,7 +426,7 @@ mod tests {
let network_graph = Arc::new(NetworkGraph::new(genesis_block.header.block_hash()));
let net_graph_msg_handler = Some(Arc::new(NetGraphMsgHandler::new(network_graph.clone(), Some(chain_source.clone()), logger.clone())));
let msg_handler = MessageHandler { chan_handler: Arc::new(test_utils::TestChannelMessageHandler::new()), route_handler: Arc::new(test_utils::TestRoutingMessageHandler::new() )};
let peer_manager = Arc::new(PeerManager::new(msg_handler, keys_manager.get_node_secret(), &seed, logger.clone(), IgnoringMessageHandler{}));
let peer_manager = Arc::new(PeerManager::new(msg_handler, keys_manager.get_node_secret(Recipient::Node).unwrap(), &seed, logger.clone(), IgnoringMessageHandler{}));
let node = Node { node: manager, net_graph_msg_handler, peer_manager, chain_monitor, persister, tx_broadcaster, network_graph, logger, best_block };
nodes.push(node);
}
Expand Down
7 changes: 7 additions & 0 deletions lightning-invoice/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1387,6 +1387,12 @@ pub enum CreationError {

/// The supplied millisatoshi amount was greater than the total bitcoin supply.
InvalidAmount,

/// Route hints were required for this invoice and were missing. Applies to
/// [phantom invoices].
///
/// [phantom invoices]: crate::utils::create_phantom_invoice
MissingRouteHints,
}

impl Display for CreationError {
Expand All @@ -1396,6 +1402,7 @@ impl Display for CreationError {
CreationError::RouteTooLong => f.write_str("The specified route has too many hops and can't be encoded"),
CreationError::TimestampOutOfBounds => f.write_str("The Unix timestamp of the supplied date is less than zero or greater than 35-bits"),
CreationError::InvalidAmount => f.write_str("The supplied millisatoshi amount was greater than the total bitcoin supply"),
CreationError::MissingRouteHints => f.write_str("The invoice required route hints and they weren't provided"),
}
}
}
Expand Down
224 changes: 219 additions & 5 deletions lightning-invoice/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ use bitcoin_hashes::Hash;
use crate::prelude::*;
use lightning::chain;
use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator};
use lightning::chain::keysinterface::{Sign, KeysInterface};
use lightning::chain::keysinterface::{Recipient, KeysInterface, Sign};
use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
use lightning::ln::channelmanager::{ChannelDetails, ChannelManager, PaymentId, PaymentSendFailure, MIN_FINAL_CLTV_EXPIRY};
use lightning::ln::channelmanager::{ChannelDetails, ChannelManager, PaymentId, PaymentSendFailure, PhantomRouteHints, MIN_FINAL_CLTV_EXPIRY, MIN_CLTV_EXPIRY_DELTA};
use lightning::ln::msgs::LightningError;
use lightning::routing::scoring::Score;
use lightning::routing::network_graph::{NetworkGraph, RoutingFees};
Expand All @@ -21,6 +21,99 @@ use core::convert::TryInto;
use core::ops::Deref;
use core::time::Duration;

#[cfg(feature = "std")]
/// Utility to create an invoice that can be paid to one of multiple nodes, or a "phantom invoice."
/// See [`PhantomKeysManager`] for more information on phantom node payments.
///
/// `phantom_route_hints` parameter:
/// * Contains channel info for all nodes participating in the phantom invoice
/// * Entries are retrieved from a call to [`ChannelManager::get_phantom_route_hints`] on each
/// participating node
/// * It is fine to cache `phantom_route_hints` and reuse it across invoices, as long as the data is
/// updated when a channel becomes disabled or closes
/// * Note that if too many channels are included in [`PhantomRouteHints::channels`], the invoice
/// may be too long for QR code scanning. To fix this, `PhantomRouteHints::channels` may be pared
/// down
///
/// `payment_hash` and `payment_secret` come from [`ChannelManager::create_inbound_payment`] or
/// [`ChannelManager::create_inbound_payment_for_hash`]. These values can be retrieved from any
/// participating node.
Comment on lines +38 to +40
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that create_invoice_from_channelmanager will call ChannelManager::create_inbound_payment. Any reason we shouldn't do that here, too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd move us further away from #1295

///
/// Note that the provided `keys_manager`'s `KeysInterface` implementation must support phantom
/// invoices in its `sign_invoice` implementation ([`PhantomKeysManager`] satisfies this
/// requirement).
///
/// [`PhantomKeysManager`]: lightning::chain::keysinterface::PhantomKeysManager
/// [`ChannelManager::get_phantom_route_hints`]: lightning::ln::channelmanager::ChannelManager::get_phantom_route_hints
/// [`PhantomRouteHints::channels`]: lightning::ln::channelmanager::PhantomRouteHints::channels
pub fn create_phantom_invoice<Signer: Sign, K: Deref>(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does it interferes with MPP ? Am I correct that you can't MPP shard to each one of the participating nodes as they won't communicate among themselves shard reception ?

Maybe we could do that in the future, if we move the MPP accounting logic from process_pending_htlc_forwards behind an abstract interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does it interferes with MPP ? Am I correct that you can't MPP shard to each one of the participating nodes as they won't communicate among themselves shard reception ?

Yep!

Maybe we could do that in the future, if we move the MPP accounting logic from process_pending_htlc_forwards behind an abstract interface.

I suggested this to @TheBlueMatt but it seems to be too much of a compromise on safety guarantees, e.g. it could allow the user to cause us to release the preimage too early. Also a bit of a layer violation.

Adding a comment clarifying this!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggested this to @TheBlueMatt but it seems to be too much of a compromise on safety guarantees, e.g. it could allow the user to cause us to release the preimage too early. Also a bit of a layer violation.

I think that's okay on the safety guarantees, if you have some two-phases commit protocol (you announce the HTLC reception to the coordinating server and then when all are received you release the preimage). IIRC, we might need such protocol if we want to coordinate well the channel monitor replicas. If it does happen, we can re-think MPP phantom invoice in the future.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean its definitely possible to provide a complicated two-phase API for this, but I'm not sure that means we should. It'd definitely be a complicated API, and if a user wants to receive a large payment they can also just generate non-phantom invoices for large payments. We can revisit this if someone really needs it, though, indeed.

amt_msat: Option<u64>, description: String, payment_hash: PaymentHash, payment_secret:
PaymentSecret, phantom_route_hints: Vec<PhantomRouteHints>, keys_manager: K, network: Currency
) -> Result<Invoice, SignOrCreationError<()>> where K::Target: KeysInterface {
if phantom_route_hints.len() == 0 {
return Err(SignOrCreationError::CreationError(CreationError::MissingRouteHints))
}
let mut invoice = InvoiceBuilder::new(network)
.description(description)
.current_timestamp()
.payment_hash(Hash::from_slice(&payment_hash.0).unwrap())
.payment_secret(payment_secret)
.min_final_cltv_expiry(MIN_FINAL_CLTV_EXPIRY.into());
if let Some(amt) = amt_msat {
invoice = invoice.amount_milli_satoshis(amt);
}

for hint in phantom_route_hints {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly would make these more difficult to filter, but I wonder if we should just have get_phantom_route_hints return RouteHints.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, nevermind. That would allow someone to call create_phantom_invoice with invalid hints.

for channel in &hint.channels {
let short_channel_id = match channel.short_channel_id {
Some(id) => id,
None => continue,
};
let forwarding_info = match &channel.counterparty.forwarding_info {
Some(info) => info.clone(),
None => continue,
};
invoice = invoice.private_route(RouteHint(vec![
RouteHintHop {
src_node_id: channel.counterparty.node_id,
short_channel_id,
fees: RoutingFees {
base_msat: forwarding_info.fee_base_msat,
proportional_millionths: forwarding_info.fee_proportional_millionths,
},
cltv_expiry_delta: forwarding_info.cltv_expiry_delta,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
},
RouteHintHop {
src_node_id: hint.real_node_pubkey,
short_channel_id: hint.phantom_scid,
fees: RoutingFees {
base_msat: 0,
proportional_millionths: 0,
},
cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA,
htlc_minimum_msat: None,
htlc_maximum_msat: None,
}])
);
}
}

let raw_invoice = match invoice.build_raw() {
Ok(inv) => inv,
Err(e) => return Err(SignOrCreationError::CreationError(e))
};
let hrp_str = raw_invoice.hrp.to_string();
let hrp_bytes = hrp_str.as_bytes();
let data_without_signature = raw_invoice.data.to_base32();
let signed_raw_invoice = raw_invoice.sign(|_| keys_manager.sign_invoice(hrp_bytes, &data_without_signature, Recipient::PhantomNode));
match signed_raw_invoice {
Ok(inv) => Ok(Invoice::from_signed(inv).unwrap()),
Err(e) => Err(SignOrCreationError::SignError(e))
}
}

#[cfg(feature = "std")]
/// Utility to construct an invoice. Generally, unless you want to do something like a custom
/// cltv_expiry, this is what you should be using to create an invoice. The reason being, this
Expand Down Expand Up @@ -118,7 +211,7 @@ where
let hrp_str = raw_invoice.hrp.to_string();
let hrp_bytes = hrp_str.as_bytes();
let data_without_signature = raw_invoice.data.to_base32();
let signed_raw_invoice = raw_invoice.sign(|_| keys_manager.sign_invoice(hrp_bytes, &data_without_signature));
let signed_raw_invoice = raw_invoice.sign(|_| keys_manager.sign_invoice(hrp_bytes, &data_without_signature, Recipient::Node));
match signed_raw_invoice {
Ok(inv) => Ok(Invoice::from_signed(inv).unwrap()),
Err(e) => Err(SignOrCreationError::SignError(e))
Expand Down Expand Up @@ -192,13 +285,17 @@ where
mod test {
use core::time::Duration;
use {Currency, Description, InvoiceDescription};
use lightning::ln::PaymentHash;
use bitcoin_hashes::Hash;
use bitcoin_hashes::sha256::Hash as Sha256;
use lightning::chain::keysinterface::PhantomKeysManager;
use lightning::ln::{PaymentPreimage, PaymentHash};
use lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY;
use lightning::ln::functional_test_utils::*;
use lightning::ln::features::InitFeatures;
use lightning::ln::msgs::ChannelMessageHandler;
use lightning::routing::router::{PaymentParameters, RouteParameters, find_route};
use lightning::util::events::MessageSendEventsProvider;
use lightning::util::enforcing_trait_impls::EnforcingSigner;
use lightning::util::events::{MessageSendEvent, MessageSendEventsProvider, Event};
use lightning::util::test_utils;
use utils::create_invoice_from_channelmanager_and_duration_since_epoch;

Expand Down Expand Up @@ -254,4 +351,121 @@ mod test {
let events = nodes[1].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 2);
}

#[test]
#[cfg(feature = "std")]
fn test_multi_node_receive() {
do_test_multi_node_receive(true);
do_test_multi_node_receive(false);
}

#[cfg(feature = "std")]
fn do_test_multi_node_receive(user_generated_pmt_hash: bool) {
let mut chanmon_cfgs = create_chanmon_cfgs(3);
let seed_1 = [42 as u8; 32];
let seed_2 = [43 as u8; 32];
let cross_node_seed = [44 as u8; 32];
chanmon_cfgs[1].keys_manager.backing = PhantomKeysManager::new(&seed_1, 43, 44, &cross_node_seed);
chanmon_cfgs[2].keys_manager.backing = PhantomKeysManager::new(&seed_2, 43, 44, &cross_node_seed);
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
let chan_0_1 = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001, InitFeatures::known(), InitFeatures::known());
nodes[0].node.handle_channel_update(&nodes[1].node.get_our_node_id(), &chan_0_1.1);
nodes[1].node.handle_channel_update(&nodes[0].node.get_our_node_id(), &chan_0_1.0);
let chan_0_2 = create_announced_chan_between_nodes_with_value(&nodes, 0, 2, 100000, 10001, InitFeatures::known(), InitFeatures::known());
nodes[0].node.handle_channel_update(&nodes[2].node.get_our_node_id(), &chan_0_2.1);
nodes[2].node.handle_channel_update(&nodes[0].node.get_our_node_id(), &chan_0_2.0);

let payment_amt = 10_000;
let (payment_preimage, payment_hash, payment_secret) = {
if user_generated_pmt_hash {
let payment_preimage = PaymentPreimage([1; 32]);
let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0[..]).into_inner());
let payment_secret = nodes[1].node.create_inbound_payment_for_hash(payment_hash, Some(payment_amt), 3600).unwrap();
(payment_preimage, payment_hash, payment_secret)
} else {
let (payment_hash, payment_secret) = nodes[1].node.create_inbound_payment(Some(payment_amt), 3600).unwrap();
let payment_preimage = nodes[1].node.get_payment_preimage(payment_hash, payment_secret).unwrap();
(payment_preimage, payment_hash, payment_secret)
}
};
let route_hints = vec![
nodes[1].node.get_phantom_route_hints(),
nodes[2].node.get_phantom_route_hints(),
];
let invoice = ::utils::create_phantom_invoice::<EnforcingSigner, &test_utils::TestKeysInterface>(Some(payment_amt), "test".to_string(), payment_hash, payment_secret, route_hints, &nodes[1].keys_manager, Currency::BitcoinTestnet).unwrap();

assert_eq!(invoice.min_final_cltv_expiry(), MIN_FINAL_CLTV_EXPIRY as u64);
assert_eq!(invoice.description(), InvoiceDescription::Direct(&Description("test".to_string())));
assert_eq!(invoice.route_hints().len(), 2);
assert!(!invoice.features().unwrap().supports_basic_mpp());

let payment_params = PaymentParameters::from_node_id(invoice.recover_payee_pub_key())
.with_features(invoice.features().unwrap().clone())
.with_route_hints(invoice.route_hints());
let params = RouteParameters {
payment_params,
final_value_msat: invoice.amount_milli_satoshis().unwrap(),
final_cltv_expiry_delta: invoice.min_final_cltv_expiry() as u32,
};
let first_hops = nodes[0].node.list_usable_channels();
let network_graph = node_cfgs[0].network_graph;
let logger = test_utils::TestLogger::new();
let scorer = test_utils::TestScorer::with_penalty(0);
let route = find_route(
&nodes[0].node.get_our_node_id(), &params, network_graph,
Some(&first_hops.iter().collect::<Vec<_>>()), &logger, &scorer,
).unwrap();
let (payment_event, fwd_idx) = {
let mut payment_hash = PaymentHash([0; 32]);
payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]);
nodes[0].node.send_payment(&route, payment_hash, &Some(invoice.payment_secret().clone())).unwrap();
let mut added_monitors = nodes[0].chain_monitor.added_monitors.lock().unwrap();
assert_eq!(added_monitors.len(), 1);
added_monitors.clear();

let mut events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
let fwd_idx = match events[0] {
MessageSendEvent::UpdateHTLCs { node_id, .. } => {
if node_id == nodes[1].node.get_our_node_id() {
1
} else { 2 }
},
_ => panic!("Unexpected event")
};
(SendEvent::from_event(events.remove(0)), fwd_idx)
};
nodes[fwd_idx].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
commitment_signed_dance!(nodes[fwd_idx], nodes[0], &payment_event.commitment_msg, false, true);

// Note that we have to "forward pending HTLCs" twice before we see the PaymentReceived as
// this "emulates" the payment taking two hops, providing some privacy to make phantom node
// payments "look real" by taking more time.
expect_pending_htlcs_forwardable_ignore!(nodes[fwd_idx]);
nodes[fwd_idx].node.process_pending_htlc_forwards();
expect_pending_htlcs_forwardable_ignore!(nodes[fwd_idx]);
nodes[fwd_idx].node.process_pending_htlc_forwards();

let payment_preimage_opt = if user_generated_pmt_hash { None } else { Some(payment_preimage) };
expect_payment_received!(&nodes[fwd_idx], payment_hash, payment_secret, payment_amt, payment_preimage_opt);
do_claim_payment_along_route(&nodes[0], &vec!(&vec!(&nodes[fwd_idx])[..]), false, payment_preimage);
let events = nodes[0].node.get_and_clear_pending_events();
assert_eq!(events.len(), 2);
match events[0] {
Event::PaymentSent { payment_preimage: ref ev_preimage, payment_hash: ref ev_hash, ref fee_paid_msat, .. } => {
assert_eq!(payment_preimage, *ev_preimage);
assert_eq!(payment_hash, *ev_hash);
assert_eq!(fee_paid_msat, &Some(0));
},
_ => panic!("Unexpected event")
}
match events[1] {
Event::PaymentPathSuccessful { payment_hash: hash, .. } => {
assert_eq!(hash, Some(payment_hash));
},
_ => panic!("Unexpected event")
}
}
}
Loading