Skip to content

Commit 87a2987

Browse files
committed
Add test asserting LSPS5 service state is persisted across restarts
We add a simple test that runs the LSPS5 flow, persists, and ensures we recover the service state after reinitializing from our `KVStore`.
1 parent 66aafee commit 87a2987

File tree

1 file changed

+167
-4
lines changed

1 file changed

+167
-4
lines changed

lightning-liquidity/tests/lsps5_integration_tests.rs

Lines changed: 167 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,23 @@
22

33
mod common;
44

5-
use common::{create_service_and_client_nodes, get_lsps_message, LSPSNodes, LiquidityNode};
5+
use common::{
6+
create_service_and_client_nodes, create_service_and_client_nodes_with_kv_stores,
7+
get_lsps_message, LSPSNodes, LiquidityNode,
8+
};
69

10+
use lightning::chain::{BestBlock, Filter};
711
use lightning::check_closed_event;
812
use lightning::events::ClosureReason;
9-
use lightning::ln::channelmanager::InterceptId;
13+
use lightning::ln::channelmanager::{ChainParameters, InterceptId};
1014
use lightning::ln::functional_test_utils::{
1115
close_channel, create_chan_between_nodes, create_chanmon_cfgs, create_network,
1216
create_node_cfgs, create_node_chanmgrs, Node,
1317
};
1418
use lightning::ln::msgs::Init;
1519
use lightning::ln::peer_handler::CustomMessageHandler;
1620
use lightning::util::hash_tables::{HashMap, HashSet};
21+
use lightning::util::test_utils::TestStore;
1722
use lightning_liquidity::events::LiquidityEvent;
1823
use lightning_liquidity::lsps0::ser::LSPSDateTime;
1924
use lightning_liquidity::lsps2::client::LSPS2ClientConfig;
@@ -34,16 +39,20 @@ use lightning_liquidity::lsps5::service::{
3439
};
3540
use lightning_liquidity::lsps5::validator::{LSPS5Validator, MAX_RECENT_SIGNATURES};
3641
use lightning_liquidity::utils::time::{DefaultTimeProvider, TimeProvider};
42+
use lightning_liquidity::LiquidityManagerSync;
3743
use lightning_liquidity::{LiquidityClientConfig, LiquidityServiceConfig};
3844

3945
use lightning_types::payment::PaymentHash;
4046

47+
use bitcoin::Network;
48+
4149
use std::str::FromStr;
4250
use std::sync::{Arc, RwLock};
4351
use std::time::Duration;
4452

45-
pub(crate) fn lsps5_test_setup<'a, 'b, 'c>(
53+
pub(crate) fn lsps5_test_setup_with_kv_stores<'a, 'b, 'c>(
4654
nodes: Vec<Node<'a, 'b, 'c>>, time_provider: Arc<dyn TimeProvider + Send + Sync>,
55+
service_kv_store: Arc<TestStore>, client_kv_store: Arc<TestStore>,
4756
) -> (LSPSNodes<'a, 'b, 'c>, LSPS5Validator) {
4857
let lsps5_service_config = LSPS5ServiceConfig::default();
4958
let service_config = LiquidityServiceConfig {
@@ -62,18 +71,28 @@ pub(crate) fn lsps5_test_setup<'a, 'b, 'c>(
6271
lsps5_client_config: Some(lsps5_client_config),
6372
};
6473

65-
let lsps_nodes = create_service_and_client_nodes(
74+
let lsps_nodes = create_service_and_client_nodes_with_kv_stores(
6675
nodes,
6776
service_config,
6877
client_config,
6978
Arc::clone(&time_provider),
79+
service_kv_store,
80+
client_kv_store,
7081
);
7182

7283
let validator = LSPS5Validator::new();
7384

7485
(lsps_nodes, validator)
7586
}
7687

88+
pub(crate) fn lsps5_test_setup<'a, 'b, 'c>(
89+
nodes: Vec<Node<'a, 'b, 'c>>, time_provider: Arc<dyn TimeProvider + Send + Sync>,
90+
) -> (LSPSNodes<'a, 'b, 'c>, LSPS5Validator) {
91+
let service_kv_store = Arc::new(TestStore::new(false));
92+
let client_kv_store = Arc::new(TestStore::new(false));
93+
lsps5_test_setup_with_kv_stores(nodes, time_provider, service_kv_store, client_kv_store)
94+
}
95+
7796
fn assert_lsps5_reject(
7897
service_node: &LiquidityNode<'_, '_, '_>, client_node: &LiquidityNode<'_, '_, '_>,
7998
) {
@@ -1479,3 +1498,147 @@ fn lsps2_state_allows_lsps5_request() {
14791498

14801499
assert_lsps5_accept(&lsps_nodes.service_node, &lsps_nodes.client_node);
14811500
}
1501+
1502+
#[test]
1503+
fn lsps5_service_handler_persistence_across_restarts() {
1504+
let chanmon_cfgs = create_chanmon_cfgs(2);
1505+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
1506+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
1507+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
1508+
1509+
// Create shared KV store for service node that will persist across restarts
1510+
let service_kv_store = Arc::new(TestStore::new(false));
1511+
let client_kv_store = Arc::new(TestStore::new(false));
1512+
1513+
let service_config = LiquidityServiceConfig {
1514+
#[cfg(lsps1_service)]
1515+
lsps1_service_config: None,
1516+
lsps2_service_config: None,
1517+
lsps5_service_config: Some(LSPS5ServiceConfig::default()),
1518+
advertise_service: true,
1519+
};
1520+
let time_provider: Arc<dyn TimeProvider + Send + Sync> = Arc::new(DefaultTimeProvider);
1521+
1522+
// Variables to carry state between scopes
1523+
let client_node_id;
1524+
let app_name = "PersistenceTestApp";
1525+
let webhook_url = "https://example.org/persistence-test";
1526+
1527+
// First scope: Setup, webhook registration, persistence, and dropping of all node objects
1528+
{
1529+
// Use the helper function with custom KV stores
1530+
let (lsps_nodes, _validator) = lsps5_test_setup_with_kv_stores(
1531+
nodes,
1532+
Arc::clone(&time_provider),
1533+
Arc::clone(&service_kv_store),
1534+
client_kv_store,
1535+
);
1536+
let LSPSNodes { service_node, client_node } = lsps_nodes;
1537+
1538+
// Establish a channel to meet LSPS5 requirements
1539+
create_chan_between_nodes(&service_node.inner, &client_node.inner);
1540+
1541+
let service_node_id = service_node.inner.node.get_our_node_id();
1542+
client_node_id = client_node.inner.node.get_our_node_id();
1543+
1544+
let client_handler = client_node.liquidity_manager.lsps5_client_handler().unwrap();
1545+
1546+
// Register a webhook to create state that needs persistence
1547+
let _request_id = client_handler
1548+
.set_webhook(service_node_id, app_name.to_string(), webhook_url.to_string())
1549+
.expect("Register webhook request should succeed");
1550+
let set_webhook_request = get_lsps_message!(client_node, service_node_id);
1551+
1552+
service_node
1553+
.liquidity_manager
1554+
.handle_custom_message(set_webhook_request, client_node_id)
1555+
.unwrap();
1556+
1557+
// Consume SendWebhookNotification event for webhook_registered
1558+
let webhook_notification_event = service_node.liquidity_manager.next_event().unwrap();
1559+
match webhook_notification_event {
1560+
LiquidityEvent::LSPS5Service(LSPS5ServiceEvent::SendWebhookNotification {
1561+
counterparty_node_id,
1562+
notification,
1563+
..
1564+
}) => {
1565+
assert_eq!(counterparty_node_id, client_node_id);
1566+
assert_eq!(notification.method, WebhookNotificationMethod::LSPS5WebhookRegistered);
1567+
},
1568+
_ => panic!("Expected SendWebhookNotification event"),
1569+
}
1570+
1571+
let set_webhook_response = get_lsps_message!(service_node, client_node_id);
1572+
client_node
1573+
.liquidity_manager
1574+
.handle_custom_message(set_webhook_response, service_node_id)
1575+
.unwrap();
1576+
1577+
let webhook_registered_event = client_node.liquidity_manager.next_event().unwrap();
1578+
match webhook_registered_event {
1579+
LiquidityEvent::LSPS5Client(LSPS5ClientEvent::WebhookRegistered {
1580+
num_webhooks,
1581+
..
1582+
}) => {
1583+
assert_eq!(num_webhooks, 1);
1584+
},
1585+
_ => panic!("Expected WebhookRegistered event"),
1586+
}
1587+
1588+
// Trigger persistence by calling persist
1589+
service_node.liquidity_manager.persist().unwrap();
1590+
1591+
// All node objects are dropped at the end of this scope
1592+
}
1593+
1594+
// Second scope: Recovery from persisted store and verification
1595+
{
1596+
// Create fresh node configurations for restart to avoid connection conflicts
1597+
let node_chanmgrs_restart = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
1598+
let nodes_restart = create_network(2, &node_cfgs, &node_chanmgrs_restart);
1599+
1600+
// Create a new LiquidityManager with the same configuration and KV store to simulate restart
1601+
let chain_params = ChainParameters {
1602+
network: Network::Testnet,
1603+
best_block: BestBlock::from_network(Network::Testnet),
1604+
};
1605+
1606+
let restarted_service_lm = LiquidityManagerSync::new_with_custom_time_provider(
1607+
nodes_restart[0].keys_manager,
1608+
nodes_restart[0].keys_manager,
1609+
nodes_restart[0].node,
1610+
None::<Arc<dyn Filter + Send + Sync>>,
1611+
Some(chain_params),
1612+
service_kv_store,
1613+
Some(service_config),
1614+
None,
1615+
Arc::clone(&time_provider),
1616+
)
1617+
.unwrap();
1618+
1619+
let restarted_service_handler = restarted_service_lm.lsps5_service_handler().unwrap();
1620+
1621+
// Verify the state was properly restored by attempting to send a notification
1622+
// This should succeed if the webhook state was properly restored
1623+
let result = restarted_service_handler.notify_payment_incoming(client_node_id);
1624+
assert!(result.is_ok(), "Notification should succeed with restored webhook state");
1625+
1626+
// Check that we get a SendWebhookNotification event, confirming the state was restored correctly
1627+
let event = restarted_service_lm.next_event();
1628+
assert!(event.is_some(), "Should have an event after sending notification");
1629+
1630+
if let Some(LiquidityEvent::LSPS5Service(LSPS5ServiceEvent::SendWebhookNotification {
1631+
counterparty_node_id,
1632+
url,
1633+
notification,
1634+
..
1635+
})) = event
1636+
{
1637+
assert_eq!(counterparty_node_id, client_node_id);
1638+
assert_eq!(url.as_str(), webhook_url);
1639+
assert_eq!(notification.method, WebhookNotificationMethod::LSPS5PaymentIncoming);
1640+
} else {
1641+
panic!("Expected SendWebhookNotification event after restart");
1642+
}
1643+
}
1644+
}

0 commit comments

Comments
 (0)