Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion rust-lightning
6 changes: 6 additions & 0 deletions src/ldk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,12 @@ pub(crate) static HOLD_PAYMENT_CLAIMABLE_ON_NODE: Mutex<Option<PublicKey>> = Mut
#[cfg(test)]
pub(crate) static HELD_PAYMENT_CLAIMABLE_COUNT: AtomicUsize = AtomicUsize::new(0);

// Test-only: the node with this pubkey emits a `push_asset_amount` greater than the channel asset
// amount on the wire in `open_channel`, regardless of the value validated by its REST layer. Used
// to model a channel counterparty whose wire client is not bound by the sender-side clamp.
#[cfg(test)]
pub(crate) static FORCE_PUSH_ASSET_AMOUNT_ON_NODE: Mutex<Option<PublicKey>> = Mutex::new(None);

pub(crate) struct LdkBackgroundServices {
stop_processing: Arc<AtomicBool>,
peer_manager: Arc<PeerManager>,
Expand Down
13 changes: 12 additions & 1 deletion src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ use tokio::{
sync::MutexGuard as TokioMutexGuard,
};

#[cfg(test)]
use crate::ldk::FORCE_PUSH_ASSET_AMOUNT_ON_NODE;
use crate::swap::{SwapData, SwapInfo, SwapString};
use crate::utils::{
check_already_initialized, check_channel_id, check_password_strength, check_password_validity,
Expand Down Expand Up @@ -3495,7 +3497,16 @@ pub(crate) async fn open_channel(
let schema = unlocked_state
.rgb_get_asset_metadata(*contract_id)?
.asset_schema;
(Some((*contract_id, payload.push_asset_amount)), Some(schema))
#[cfg(not(test))]
let wire_push_asset_amount = payload.push_asset_amount;
#[cfg(test)]
let wire_push_asset_amount = match *FORCE_PUSH_ASSET_AMOUNT_ON_NODE.lock().unwrap() {
Some(node) if node == unlocked_state.channel_manager.get_our_node_id() => {
Some(*asset_amount + 1)
}
_ => payload.push_asset_amount,
};
(Some((*contract_id, wire_push_asset_amount)), Some(schema))
} else {
let balance = unlocked_state.rgb_get_btc_balance(true)?;
if payload.capacity_sat > balance.vanilla.spendable {
Expand Down
5 changes: 3 additions & 2 deletions src/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ use tracing_test::traced_test;
use crate::disk::LDK_LOGS_FILE;
use crate::error::APIErrorResponse;
use crate::ldk::{
FEE_RATE, HELD_PAYMENT_CLAIMABLE_COUNT, HOLD_PAYMENT_CLAIMABLE_ON_NODE,
IGNORE_INBOUND_CHANNELS_ON_NODE,
FEE_RATE, FORCE_PUSH_ASSET_AMOUNT_ON_NODE, HELD_PAYMENT_CLAIMABLE_COUNT,
HOLD_PAYMENT_CLAIMABLE_ON_NODE, IGNORE_INBOUND_CHANNELS_ON_NODE,
};
use crate::routes::{
AddressResponse, AssetBalanceRequest, AssetBalanceResponse, AssetCFA, AssetIFA, AssetNIA,
Expand Down Expand Up @@ -2281,6 +2281,7 @@ mod openchannel_optional_addr;
mod openchannel_push_asset_amount;
mod out_of_band;
mod payment;
mod push_asset_amount_above_chan_amt;
mod refuse_high_fees;
mod restart;
mod send_receive;
Expand Down
64 changes: 64 additions & 0 deletions src/test/push_asset_amount_above_chan_amt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use super::*;

const TEST_DIR_BASE: &str = "tmp/push_asset_amount_above_chan_amt/";

/// A counterparty sending a `push_asset_amount` greater than the channel asset amount used to
/// underflow `remote_rgb_amount` on the acceptor, panicking its event handler. The acceptor must
/// reject the funding and stay alive.
#[serial_test::serial]
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[traced_test]
async fn push_asset_amount_above_chan_amt() {
initialize();

let test_dir_node1 = format!("{TEST_DIR_BASE}node1");
let test_dir_node2 = format!("{TEST_DIR_BASE}node2");
let (node1_addr, _) = start_node(&test_dir_node1, NODE1_PEER_PORT, false).await;
let (node2_addr, _) = start_node(&test_dir_node2, NODE2_PEER_PORT, false).await;

fund_and_create_utxos(node1_addr, None).await;
fund_and_create_utxos(node2_addr, None).await;

let node1_pubkey = node_info(node1_addr).await.pubkey;
let node2_pubkey = node_info(node2_addr).await.pubkey;

let asset_id = issue_asset_nia(node1_addr).await.asset_id;

// node1 puts more than the channel asset amount on the wire, bypassing the REST clamp that the
// push_asset_amount below satisfies
let _force_guard = NodeOverrideGuard::set(&FORCE_PUSH_ASSET_AMOUNT_ON_NODE, &node1_pubkey);

open_channel_raw(
node1_addr,
&node2_pubkey,
Some(NODE2_PEER_PORT),
Some(100_000),
None,
Some(100),
Some(&asset_id),
Some(0),
None,
None,
None,
true,
true,
)
.await
.unwrap();

// node2 rejects the funding, so node1's pending channel is discarded
let t_0 = OffsetDateTime::now_utc();
loop {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
if list_channels(node1_addr).await.is_empty() {
break;
}
if (OffsetDateTime::now_utc() - t_0).as_seconds_f32() > 30.0 {
panic!("initiator channel was not discarded");
}
}

// with the underflow node2 would have panicked in its event handler
node_info(node2_addr).await;
assert!(list_channels(node2_addr).await.is_empty());
}
Loading