Skip to content

Commit 2b3b00f

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

File tree

1 file changed

+191
-1
lines changed

1 file changed

+191
-1
lines changed

lightning/src/ln/payment_tests.rs

Lines changed: 191 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ use crate::chain::channelmonitor::{ANTI_REORG_DELAY, LATENCY_GRACE_PERIOD_BLOCKS
1616
use crate::chain::transaction::OutPoint;
1717
use crate::chain::keysinterface::{EntropySource, KeysInterface};
1818
use crate::ln::channel::EXPIRE_PREV_CONFIG_TICKS;
19-
use crate::ln::channelmanager::{self, BREAKDOWN_TIMEOUT, ChannelManager, MPP_TIMEOUT_TICKS, MIN_CLTV_EXPIRY_DELTA, PaymentId, PaymentSendFailure, IDEMPOTENCY_TIMEOUT_TICKS};
19+
use crate::ln::channelmanager::{self, BREAKDOWN_TIMEOUT, ChannelManager, MPP_TIMEOUT_TICKS, MIN_CLTV_EXPIRY_DELTA, PaymentId, PaymentSendFailure, IDEMPOTENCY_TIMEOUT_TICKS, Retry};
20+
use crate::ln::features::InvoiceFeatures;
2021
use crate::ln::msgs;
2122
use crate::ln::msgs::ChannelMessageHandler;
2223
use crate::routing::gossip::RoutingFees;
@@ -34,6 +35,11 @@ use crate::prelude::*;
3435

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

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

0 commit comments

Comments
 (0)