Skip to content

Commit da266f2

Browse files
Test ChannelManager automatic retries
TODO: more tests
1 parent 8c4590f commit da266f2

File tree

1 file changed

+191
-0
lines changed

1 file changed

+191
-0
lines changed

lightning/src/ln/payment_tests.rs

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ use crate::chain::transaction::OutPoint;
1717
use crate::chain::keysinterface::{EntropySource, KeysInterface};
1818
use crate::ln::channel::EXPIRE_PREV_CONFIG_TICKS;
1919
use crate::ln::channelmanager::{self, BREAKDOWN_TIMEOUT, ChannelManager, MPP_TIMEOUT_TICKS, MIN_CLTV_EXPIRY_DELTA, PaymentId, PaymentSendFailure, IDEMPOTENCY_TIMEOUT_TICKS};
20+
use crate::ln::features::InvoiceFeatures;
2021
use crate::ln::msgs;
2122
use crate::ln::msgs::ChannelMessageHandler;
23+
use crate::ln::outbound_payment::Retry;
2224
use crate::routing::gossip::RoutingFees;
2325
use crate::routing::router::{get_route, PaymentParameters, RouteHint, RouteHintHop, RouteParameters};
2426
use crate::util::events::{ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider};
@@ -34,6 +36,11 @@ use crate::prelude::*;
3436

3537
use crate::ln::functional_test_utils::*;
3638
use crate::routing::gossip::NodeId;
39+
#[cfg(feature = "std")]
40+
use {
41+
crate::util::time::tests::SinceEpoch,
42+
std::time::{SystemTime, Duration}
43+
};
3744

3845
#[test]
3946
fn retry_single_path_payment() {
@@ -1564,3 +1571,187 @@ fn do_test_intercepted_payment(test: InterceptTest) {
15641571
assert_eq!(unknown_intercept_id_err , APIError::APIMisuseError { err: format!("Payment with intercept id {} not found", log_bytes!(intercept_id.0)) });
15651572
}
15661573
}
1574+
1575+
#[derive(PartialEq)]
1576+
enum AutoRetry {
1577+
Success,
1578+
FailAttempts,
1579+
FailTimeout,
1580+
FailOnRestart,
1581+
}
1582+
1583+
#[test]
1584+
fn automatic_retries() {
1585+
do_automatic_retries(AutoRetry::Success);
1586+
do_automatic_retries(AutoRetry::FailAttempts);
1587+
do_automatic_retries(AutoRetry::FailTimeout);
1588+
do_automatic_retries(AutoRetry::FailOnRestart);
1589+
}
1590+
fn do_automatic_retries(test: AutoRetry) {
1591+
// Test basic automatic payment retries in ChannelManager. See individual `test` variant comments
1592+
// below.
1593+
let chanmon_cfgs = create_chanmon_cfgs(3);
1594+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
1595+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
1596+
1597+
let persister;
1598+
let new_chain_monitor;
1599+
let node_0_deserialized;
1600+
1601+
let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
1602+
let channel_id_1 = create_announced_chan_between_nodes(&nodes, 0, 1, channelmanager::provided_init_features(), channelmanager::provided_init_features()).2;
1603+
let channel_id_2 = create_announced_chan_between_nodes(&nodes, 2, 1, channelmanager::provided_init_features(), channelmanager::provided_init_features()).2;
1604+
1605+
// Marshall data to send the payment
1606+
#[cfg(feature = "std")]
1607+
let payment_expiry_secs = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() + 60 * 60;
1608+
#[cfg(not(feature = "std"))]
1609+
let payment_expiry_secs = 60 * 60;
1610+
let amt_msat = 1000;
1611+
let mut invoice_features = InvoiceFeatures::empty();
1612+
invoice_features.set_variable_length_onion_required();
1613+
invoice_features.set_payment_secret_required();
1614+
invoice_features.set_basic_mpp_optional();
1615+
let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id())
1616+
.with_expiry_time(payment_expiry_secs as u64)
1617+
.with_features(invoice_features);
1618+
let route_params = RouteParameters {
1619+
payment_params,
1620+
final_value_msat: amt_msat,
1621+
final_cltv_expiry_delta: TEST_FINAL_CLTV,
1622+
};
1623+
let (_, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], amt_msat);
1624+
1625+
macro_rules! pass_failed_attempt_with_retry_along_path {
1626+
($failing_channel_id: expr) => {
1627+
// Send a payment attempt that fails due to lack of liquidity on the second hop
1628+
check_added_monitors!(nodes[0], 1);
1629+
let update_0 = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id());
1630+
let mut update_add = update_0.update_add_htlcs[0].clone();
1631+
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &update_add);
1632+
commitment_signed_dance!(nodes[1], nodes[0], &update_0.commitment_signed, false, true);
1633+
expect_pending_htlcs_forwardable_ignore!(nodes[1]);
1634+
nodes[1].node.process_pending_htlc_forwards();
1635+
expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1],
1636+
vec![HTLCDestination::NextHopChannel {
1637+
node_id: Some(nodes[2].node.get_our_node_id()),
1638+
channel_id: $failing_channel_id,
1639+
}]);
1640+
nodes[1].node.process_pending_htlc_forwards();
1641+
let update_1 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
1642+
check_added_monitors!(&nodes[1], 1);
1643+
assert!(update_1.update_fail_htlcs.len() == 1);
1644+
let fail_msg = update_1.update_fail_htlcs[0].clone();
1645+
1646+
nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &fail_msg);
1647+
commitment_signed_dance!(nodes[0], nodes[1], update_1.commitment_signed, false);
1648+
1649+
// Ensure the attempt fails and a new PendingHTLCsForwardable event is generated for the retry
1650+
let mut events = nodes[0].node.get_and_clear_pending_events();
1651+
assert_eq!(events.len(), 2);
1652+
match events[0] {
1653+
Event::PaymentPathFailed { payment_hash: ev_payment_hash, payment_failed_permanently, .. } => {
1654+
assert_eq!(payment_hash, ev_payment_hash);
1655+
assert_eq!(payment_failed_permanently, false);
1656+
},
1657+
_ => panic!("Unexpected event"),
1658+
}
1659+
match events[1] {
1660+
Event::PendingHTLCsForwardable { .. } => {},
1661+
_ => panic!("Unexpected event"),
1662+
}
1663+
}
1664+
}
1665+
1666+
if test == AutoRetry::Success {
1667+
// Test that we can succeed on the first retry.
1668+
nodes[0].node.send_payment_with_retry(payment_hash, &Some(payment_secret), PaymentId(payment_hash.0), route_params, Retry::Attempts(1)).unwrap();
1669+
pass_failed_attempt_with_retry_along_path!(channel_id_2);
1670+
1671+
// Open a new channel with liquidity on the second hop so we can find a route for the retry
1672+
// attempt, since the initial second hop channel will be excluded from pathfinding
1673+
create_announced_chan_between_nodes(&nodes, 1, 2, channelmanager::provided_init_features(), channelmanager::provided_init_features());
1674+
1675+
// We retry payments in `process_pending_htlc_forwards`
1676+
nodes[0].node.process_pending_htlc_forwards();
1677+
check_added_monitors!(nodes[0], 1);
1678+
let mut msg_events = nodes[0].node.get_and_clear_pending_msg_events();
1679+
assert_eq!(msg_events.len(), 1);
1680+
pass_along_path(&nodes[0], &[&nodes[1], &nodes[2]], amt_msat, payment_hash, Some(payment_secret), msg_events.pop().unwrap(), true, None);
1681+
claim_payment_along_route(&nodes[0], &[&[&nodes[1], &nodes[2]]], false, payment_preimage);
1682+
} else if test == AutoRetry::FailAttempts {
1683+
// Ensure ChannelManager will not retry a payment if it has run out of payment attempts.
1684+
nodes[0].node.send_payment_with_retry(payment_hash, &Some(payment_secret), PaymentId(payment_hash.0), route_params, Retry::Attempts(1)).unwrap();
1685+
pass_failed_attempt_with_retry_along_path!(channel_id_2);
1686+
1687+
// Open a new channel with no liquidity on the second hop so we can find a (bad) route for
1688+
// the retry attempt, since the initial second hop channel will be excluded from pathfinding
1689+
let channel_id_3 = create_announced_chan_between_nodes(&nodes, 2, 1, channelmanager::provided_init_features(), channelmanager::provided_init_features()).2;
1690+
1691+
// We retry payments in `process_pending_htlc_forwards`
1692+
nodes[0].node.process_pending_htlc_forwards();
1693+
pass_failed_attempt_with_retry_along_path!(channel_id_3);
1694+
1695+
// Ensure we won't retry a second time.
1696+
nodes[0].node.process_pending_htlc_forwards();
1697+
nodes[0].logger.assert_log_regex("lightning::ln::channelmanager".to_string(), regex::Regex::new(r"Payment [0-9a-f]* is not retryable").unwrap(), 1);
1698+
let mut events = nodes[0].node.get_and_clear_pending_events();
1699+
assert_eq!(events.len(), 1);
1700+
match events[0] {
1701+
Event::PaymentFailed { payment_hash: ref ev_payment_hash, payment_id: ref ev_payment_id } => {
1702+
assert_eq!(payment_hash, *ev_payment_hash);
1703+
assert_eq!(PaymentId(payment_hash.0), *ev_payment_id);
1704+
},
1705+
_ => panic!("Unexpected event"),
1706+
}
1707+
} else if test == AutoRetry::FailTimeout {
1708+
#[cfg(not(feature = "no-std"))] {
1709+
// Ensure ChannelManager will not retry a payment if it times out due to Retry::Timeout.
1710+
nodes[0].node.send_payment_with_retry(payment_hash, &Some(payment_secret), PaymentId(payment_hash.0), route_params, Retry::Timeout(Duration::from_secs(60))).unwrap();
1711+
pass_failed_attempt_with_retry_along_path!(channel_id_2);
1712+
1713+
// Advance the time so the second attempt fails due to timeout.
1714+
SinceEpoch::advance(Duration::from_secs(61));
1715+
nodes[0].node.process_pending_htlc_forwards();
1716+
nodes[0].logger.assert_log_regex("lightning::ln::channelmanager".to_string(), regex::Regex::new(r"Payment [0-9a-f]* is not retryable").unwrap(), 1);
1717+
let mut events = nodes[0].node.get_and_clear_pending_events();
1718+
assert_eq!(events.len(), 1);
1719+
match events[0] {
1720+
Event::PaymentFailed { payment_hash: ref ev_payment_hash, payment_id: ref ev_payment_id } => {
1721+
assert_eq!(payment_hash, *ev_payment_hash);
1722+
assert_eq!(PaymentId(payment_hash.0), *ev_payment_id);
1723+
},
1724+
_ => panic!("Unexpected event"),
1725+
}
1726+
}
1727+
} else if test == AutoRetry::FailOnRestart {
1728+
// Ensure ChannelManager will not retry a payment after restart, even if there were retry
1729+
// attempts remaining prior to restart.
1730+
nodes[0].node.send_payment_with_retry(payment_hash, &Some(payment_secret), PaymentId(payment_hash.0), route_params, Retry::Attempts(2)).unwrap();
1731+
pass_failed_attempt_with_retry_along_path!(channel_id_2);
1732+
1733+
// Open a new channel with no liquidity on the second hop so we can find a (bad) route for
1734+
// the retry attempt, since the initial second hop channel will be excluded from pathfinding
1735+
let channel_id_3 = create_announced_chan_between_nodes(&nodes, 2, 1, channelmanager::provided_init_features(), channelmanager::provided_init_features()).2;
1736+
1737+
// Ensure the first retry attempt fails, with 1 retry attempt remaining
1738+
nodes[0].node.process_pending_htlc_forwards();
1739+
pass_failed_attempt_with_retry_along_path!(channel_id_3);
1740+
1741+
// Restart the node and ensure that ChannelManager does not use its remaining retry attempt
1742+
let node_encoded = nodes[0].node.encode();
1743+
let chan_1_monitor_serialized = get_monitor!(nodes[0], channel_id_1).encode();
1744+
reload_node!(nodes[0], node_encoded, &[&chan_1_monitor_serialized], persister, new_chain_monitor, node_0_deserialized);
1745+
nodes[0].node.process_pending_htlc_forwards();
1746+
nodes[0].logger.assert_log_regex("lightning::ln::channelmanager".to_string(), regex::Regex::new(r"Payment [0-9a-f]* is not retryable").unwrap(), 1);
1747+
let mut events = nodes[0].node.get_and_clear_pending_events();
1748+
assert_eq!(events.len(), 1);
1749+
match events[0] {
1750+
Event::PaymentFailed { payment_hash: ref ev_payment_hash, payment_id: ref ev_payment_id } => {
1751+
assert_eq!(payment_hash, *ev_payment_hash);
1752+
assert_eq!(PaymentId(payment_hash.0), *ev_payment_id);
1753+
},
1754+
_ => panic!("Unexpected event"),
1755+
}
1756+
}
1757+
}

0 commit comments

Comments
 (0)