|
| 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