Skip to content

Commit 2e507b6

Browse files
committed
test: demonstrate rgb_send_lock stuck when ChannelPending is blocked
Open a vanilla channel but deliberately skip mining, so the funding TX stays in the mempool and the ChannelPending event never fires. On the current code the lock is held from openchannel until ChannelPending, which means a second channel open returns 403 indefinitely. This test fails on the unfixed code and passes once the lock is released at FundingGenerationReady instead. Ref: #111
1 parent 9181763 commit 2e507b6

2 files changed

Lines changed: 110 additions & 0 deletions

File tree

src/test/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1892,6 +1892,7 @@ mod lock_unlock_changepassword;
18921892
mod multi_hop;
18931893
mod multi_open_close;
18941894
mod open_after_double_send;
1895+
mod openchannel_close_before_funding;
18951896
mod openchannel_fail;
18961897
mod openchannel_optional_addr;
18971898
mod openchannel_push_asset_amount;
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
use super::*;
2+
3+
const TEST_DIR_BASE: &str = "tmp/openchannel_close_before_funding/";
4+
5+
/// Test that the rgb_send_lock is released after FundingGenerationReady completes,
6+
/// even when the funding transaction has not yet been confirmed on-chain.
7+
///
8+
/// Bug scenario: the rgb_send_lock is acquired when the openchannel API is called
9+
/// and only released when the ChannelPending event fires (which requires the
10+
/// funding TX to be mined with sufficient confirmations). If mining is delayed
11+
/// or the channel stalls between FundingGenerationReady and ChannelPending, the
12+
/// lock stays held indefinitely — blocking all subsequent channel opens, asset
13+
/// issuance, and on-chain RGB sends.
14+
///
15+
/// This test opens a vanilla channel and deliberately does NOT mine afterward.
16+
/// After FundingGenerationReady has completed (funding TX in the mempool), it
17+
/// attempts a second channel open to a different peer:
18+
///
19+
/// - Without the fix: 403 Forbidden (rgb_send_lock still held).
20+
/// - With the fix: 200 OK (lock released at FundingGenerationReady).
21+
///
22+
/// Ref: https://github.com/RGB-Tools/rgb-lightning-node/issues/111
23+
#[serial_test::serial]
24+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
25+
#[traced_test]
26+
async fn openchannel_lock_released_after_funding_generated() {
27+
initialize();
28+
29+
let test_dir_node1 = format!("{TEST_DIR_BASE}node1");
30+
let test_dir_node2 = format!("{TEST_DIR_BASE}node2");
31+
let test_dir_node3 = format!("{TEST_DIR_BASE}node3");
32+
33+
let (node1_addr, _) = start_node(&test_dir_node1, NODE1_PEER_PORT, false).await;
34+
let (node2_addr, _) = start_node(&test_dir_node2, NODE2_PEER_PORT, false).await;
35+
let (node3_addr, _) = start_node(&test_dir_node3, NODE3_PEER_PORT, false).await;
36+
37+
// Fund node1 with enough UTXOs for two channel opens
38+
fund_and_create_utxos(node1_addr, Some(10)).await;
39+
40+
let node2_pubkey = node_info(node2_addr).await.pubkey;
41+
let node3_pubkey = node_info(node3_addr).await.pubkey;
42+
43+
// Open a vanilla channel from node1 -> node2.
44+
// This sets rgb_send_lock = true and begins the LDK handshake.
45+
let payload = OpenChannelRequest {
46+
peer_pubkey_and_opt_addr: format!("{node2_pubkey}@127.0.0.1:{NODE2_PEER_PORT}"),
47+
capacity_sat: 100_000,
48+
push_msat: 0,
49+
asset_amount: None,
50+
asset_id: None,
51+
push_asset_amount: None,
52+
public: true,
53+
with_anchors: true,
54+
fee_base_msat: None,
55+
fee_proportional_millionths: None,
56+
temporary_channel_id: None,
57+
};
58+
let res = reqwest::Client::new()
59+
.post(format!("http://{node1_addr}/openchannel"))
60+
.json(&payload)
61+
.send()
62+
.await
63+
.unwrap();
64+
assert_eq!(res.status(), reqwest::StatusCode::OK);
65+
66+
// Wait for FundingGenerationReady to complete (PSBT creation, signing,
67+
// funding_transaction_generated). On localhost this takes a few seconds.
68+
//
69+
// IMPORTANT: we do NOT mine any blocks, so the funding TX stays in the
70+
// mempool and ChannelPending will never fire.
71+
tokio::time::sleep(std::time::Duration::from_secs(10)).await;
72+
73+
// Attempt a second channel from node1 -> node3.
74+
//
75+
// Without the fix the lock is still held (waiting for ChannelPending, which
76+
// needs mining) and this returns 403 Forbidden.
77+
//
78+
// With the fix the lock was already released at FundingGenerationReady and
79+
// this succeeds.
80+
let payload2 = OpenChannelRequest {
81+
peer_pubkey_and_opt_addr: format!("{node3_pubkey}@127.0.0.1:{NODE3_PEER_PORT}"),
82+
capacity_sat: 100_000,
83+
push_msat: 0,
84+
asset_amount: None,
85+
asset_id: None,
86+
push_asset_amount: None,
87+
public: true,
88+
with_anchors: true,
89+
fee_base_msat: None,
90+
fee_proportional_millionths: None,
91+
temporary_channel_id: None,
92+
};
93+
let res = reqwest::Client::new()
94+
.post(format!("http://{node1_addr}/openchannel"))
95+
.json(&payload2)
96+
.send()
97+
.await
98+
.unwrap();
99+
100+
assert_ne!(
101+
res.status(),
102+
reqwest::StatusCode::FORBIDDEN,
103+
"rgb_send_lock is stuck: the node cannot open new channels while \
104+
waiting for a previous funding TX to confirm (ChannelPending blocked)"
105+
);
106+
assert_eq!(res.status(), reqwest::StatusCode::OK);
107+
108+
shutdown(&[node1_addr, node2_addr, node3_addr]).await;
109+
}

0 commit comments

Comments
 (0)