@@ -16,7 +16,8 @@ use crate::chain::ChannelMonitorUpdateStatus;
1616use crate :: events:: { ClosureReason , Event , FundingInfo , HTLCHandlingFailureType } ;
1717use crate :: ln:: chan_utils;
1818use crate :: ln:: channel:: {
19- CHANNEL_ANNOUNCEMENT_PROPAGATION_DELAY , FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE ,
19+ ANCHOR_OUTPUT_VALUE_SATOSHI , CHANNEL_ANNOUNCEMENT_PROPAGATION_DELAY ,
20+ FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE ,
2021} ;
2122use crate :: ln:: channelmanager:: { provided_init_features, PaymentId , BREAKDOWN_TIMEOUT } ;
2223use crate :: ln:: functional_test_utils:: * ;
@@ -6857,3 +6858,353 @@ fn test_splice_rbf_rejects_own_low_feerate_after_several_attempts() {
68576858 other => panic ! ( "Expected SpliceFailed, got {:?}" , other) ,
68586859 }
68596860}
6861+
6862+ #[ test]
6863+ fn test_0reserve_splice ( ) {
6864+ let mut config = test_default_channel_config ( ) ;
6865+ config. channel_handshake_config . negotiate_anchors_zero_fee_htlc_tx = false ;
6866+ config. channel_handshake_config . negotiate_anchor_zero_fee_commitments = false ;
6867+ let a = do_test_0reserve_splice_holder_validation ( false , false , false , config. clone ( ) ) ;
6868+ let _b = do_test_0reserve_splice_holder_validation ( true , false , false , config. clone ( ) ) ;
6869+ let _c = do_test_0reserve_splice_holder_validation ( false , true , false , config. clone ( ) ) ;
6870+ let _d = do_test_0reserve_splice_holder_validation ( true , true , false , config. clone ( ) ) ;
6871+
6872+ let _e = do_test_0reserve_splice_holder_validation ( false , false , true , config. clone ( ) ) ;
6873+ let _f = do_test_0reserve_splice_holder_validation ( true , false , true , config. clone ( ) ) ;
6874+ let _g = do_test_0reserve_splice_holder_validation ( false , true , true , config. clone ( ) ) ;
6875+ let _h = do_test_0reserve_splice_holder_validation ( true , true , true , config. clone ( ) ) ;
6876+
6877+ assert_eq ! ( a, ChannelTypeFeatures :: only_static_remote_key( ) ) ;
6878+
6879+ config. channel_handshake_config . negotiate_anchors_zero_fee_htlc_tx = true ;
6880+ config. channel_handshake_config . negotiate_anchor_zero_fee_commitments = false ;
6881+ let a = do_test_0reserve_splice_holder_validation ( false , false , false , config. clone ( ) ) ;
6882+ let _b = do_test_0reserve_splice_holder_validation ( true , false , false , config. clone ( ) ) ;
6883+ let _c = do_test_0reserve_splice_holder_validation ( false , true , false , config. clone ( ) ) ;
6884+ let _d = do_test_0reserve_splice_holder_validation ( true , true , false , config. clone ( ) ) ;
6885+
6886+ let _e = do_test_0reserve_splice_holder_validation ( false , false , true , config. clone ( ) ) ;
6887+ let _f = do_test_0reserve_splice_holder_validation ( true , false , true , config. clone ( ) ) ;
6888+ let _g = do_test_0reserve_splice_holder_validation ( false , true , true , config. clone ( ) ) ;
6889+ let _h = do_test_0reserve_splice_holder_validation ( true , true , true , config. clone ( ) ) ;
6890+
6891+ assert_eq ! ( a, ChannelTypeFeatures :: anchors_zero_htlc_fee_and_dependencies( ) ) ;
6892+
6893+ let mut config = test_default_channel_config ( ) ;
6894+ config. channel_handshake_config . negotiate_anchors_zero_fee_htlc_tx = false ;
6895+ config. channel_handshake_config . negotiate_anchor_zero_fee_commitments = false ;
6896+ let a = do_test_0reserve_splice_counterparty_validation ( false , false , false , config. clone ( ) ) ;
6897+ let _b = do_test_0reserve_splice_counterparty_validation ( true , false , false , config. clone ( ) ) ;
6898+ let _c = do_test_0reserve_splice_counterparty_validation ( false , true , false , config. clone ( ) ) ;
6899+ let _d = do_test_0reserve_splice_counterparty_validation ( true , true , false , config. clone ( ) ) ;
6900+
6901+ let _e = do_test_0reserve_splice_counterparty_validation ( false , false , true , config. clone ( ) ) ;
6902+ let _f = do_test_0reserve_splice_counterparty_validation ( true , false , true , config. clone ( ) ) ;
6903+ let _g = do_test_0reserve_splice_counterparty_validation ( false , true , true , config. clone ( ) ) ;
6904+ let _h = do_test_0reserve_splice_counterparty_validation ( true , true , true , config. clone ( ) ) ;
6905+ assert_eq ! ( a, ChannelTypeFeatures :: only_static_remote_key( ) ) ;
6906+
6907+ config. channel_handshake_config . negotiate_anchors_zero_fee_htlc_tx = true ;
6908+ config. channel_handshake_config . negotiate_anchor_zero_fee_commitments = false ;
6909+ let a = do_test_0reserve_splice_counterparty_validation ( false , false , false , config. clone ( ) ) ;
6910+ let _b = do_test_0reserve_splice_counterparty_validation ( true , false , false , config. clone ( ) ) ;
6911+ let _c = do_test_0reserve_splice_counterparty_validation ( false , true , false , config. clone ( ) ) ;
6912+ let _d = do_test_0reserve_splice_counterparty_validation ( true , true , false , config. clone ( ) ) ;
6913+
6914+ let _e = do_test_0reserve_splice_counterparty_validation ( false , false , true , config. clone ( ) ) ;
6915+ let _f = do_test_0reserve_splice_counterparty_validation ( true , false , true , config. clone ( ) ) ;
6916+ let _g = do_test_0reserve_splice_counterparty_validation ( false , true , true , config. clone ( ) ) ;
6917+ let _h = do_test_0reserve_splice_counterparty_validation ( true , true , true , config. clone ( ) ) ;
6918+ assert_eq ! ( a, ChannelTypeFeatures :: anchors_zero_htlc_fee_and_dependencies( ) ) ;
6919+
6920+ // TODO: Skip 0FC channels for now as these always have an output on the commitment, the P2A
6921+ // output. We will be able to withdraw up to the dust limit of the funding script, which
6922+ // is checked in interactivetx. Still need to double check whether that's what we actually
6923+ // want.
6924+ }
6925+
6926+ #[ cfg( test) ]
6927+ fn do_test_0reserve_splice_holder_validation (
6928+ splice_passes : bool , counterparty_has_output : bool , node_0_is_initiator : bool ,
6929+ mut config : UserConfig ,
6930+ ) -> ChannelTypeFeatures {
6931+ use crate :: ln:: htlc_reserve_unit_tests:: setup_0reserve_no_outputs_channels;
6932+
6933+ let chanmon_cfgs = create_chanmon_cfgs ( 2 ) ;
6934+ let node_cfgs = create_node_cfgs ( 2 , & chanmon_cfgs) ;
6935+ config. channel_handshake_config . announced_channel_max_inbound_htlc_value_in_flight_percentage =
6936+ 100 ;
6937+ let node_chanmgrs =
6938+ create_node_chanmgrs ( 2 , & node_cfgs, & [ Some ( config. clone ( ) ) , Some ( config. clone ( ) ) ] ) ;
6939+ let nodes = create_network ( 2 , & node_cfgs, & node_chanmgrs) ;
6940+
6941+ let _node_id_0 = nodes[ 0 ] . node . get_our_node_id ( ) ;
6942+ let _node_id_1 = nodes[ 1 ] . node . get_our_node_id ( ) ;
6943+
6944+ let channel_value_sat = 100_000 ;
6945+ // Some dust limit, does not matter
6946+ let dust_limit_satoshis = 546 ;
6947+
6948+ let ( channel_id, _tx) =
6949+ setup_0reserve_no_outputs_channels ( & nodes, channel_value_sat, dust_limit_satoshis) ;
6950+ let details = & nodes[ 0 ] . node . list_channels ( ) [ 0 ] ;
6951+ let channel_type = details. channel_type . clone ( ) . unwrap ( ) ;
6952+
6953+ let feerate = 253 ;
6954+ let spiked_feerate = if channel_type == ChannelTypeFeatures :: only_static_remote_key ( ) {
6955+ feerate * FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32
6956+ } else if channel_type == ChannelTypeFeatures :: anchors_zero_htlc_fee_and_dependencies ( ) {
6957+ feerate
6958+ } else {
6959+ panic ! ( "Unexpected channel type" ) ;
6960+ } ;
6961+ let anchors_sat =
6962+ if channel_type == ChannelTypeFeatures :: anchors_zero_htlc_fee_and_dependencies ( ) {
6963+ ANCHOR_OUTPUT_VALUE_SATOSHI * 2
6964+ } else {
6965+ 0
6966+ } ;
6967+
6968+ let initiator_value_to_self_sat = if counterparty_has_output {
6969+ send_payment ( & nodes[ 0 ] , & [ & nodes[ 1 ] ] , channel_value_sat / 2 * 1_000 ) ;
6970+ channel_value_sat / 2
6971+ } else if !node_0_is_initiator {
6972+ let tx_fee_msat = chan_utils:: commit_tx_fee_sat ( spiked_feerate, 2 , & channel_type) * 1000 ;
6973+ let node_0_details = & nodes[ 0 ] . node . list_channels ( ) [ 0 ] ;
6974+ let outbound_capacity_msat = node_0_details. outbound_capacity_msat ;
6975+ let available_capacity_msat = node_0_details. next_outbound_htlc_limit_msat ;
6976+ assert_eq ! ( outbound_capacity_msat, ( channel_value_sat - anchors_sat) * 1000 ) ;
6977+ assert_eq ! ( available_capacity_msat, outbound_capacity_msat - tx_fee_msat) ;
6978+ send_payment ( & nodes[ 0 ] , & [ & nodes[ 1 ] ] , available_capacity_msat) ;
6979+
6980+ // Make sure node 0 has no output on the commitment at this point
6981+ let node_0_to_local_output_msat = channel_value_sat * 1000
6982+ - available_capacity_msat
6983+ - anchors_sat * 1000
6984+ - chan_utils:: commit_tx_fee_sat ( feerate, 0 , & channel_type) * 1000 ;
6985+ assert ! ( node_0_to_local_output_msat / 1000 < dust_limit_satoshis) ;
6986+ let commit_tx = & get_local_commitment_txn ! ( nodes[ 0 ] , channel_id) [ 0 ] ;
6987+ assert_eq ! ( commit_tx. output. len( ) , if anchors_sat == 0 { 1 } else { 2 } ) ;
6988+ assert_eq ! (
6989+ commit_tx. output. last( ) . unwrap( ) . value,
6990+ Amount :: from_sat( available_capacity_msat / 1000 )
6991+ ) ;
6992+ if anchors_sat != 0 {
6993+ assert_eq ! ( commit_tx. output[ 0 ] . value, Amount :: from_sat( 330 ) ) ;
6994+ }
6995+
6996+ available_capacity_msat / 1000
6997+ } else {
6998+ channel_value_sat
6999+ } ;
7000+
7001+ // The estimated fees to splice out a single output at 253sat/kw
7002+ let estimated_fees = 183 ;
7003+ let splice_out_max_value = if counterparty_has_output && node_0_is_initiator {
7004+ let commit_tx_fee_sat = chan_utils:: commit_tx_fee_sat ( spiked_feerate, 1 , & channel_type) ;
7005+ Amount :: from_sat (
7006+ initiator_value_to_self_sat - commit_tx_fee_sat - anchors_sat - estimated_fees,
7007+ )
7008+ } else if !counterparty_has_output && node_0_is_initiator {
7009+ let commit_tx_fee_sat = chan_utils:: commit_tx_fee_sat ( spiked_feerate, 0 , & channel_type) ;
7010+ Amount :: from_sat (
7011+ initiator_value_to_self_sat
7012+ - commit_tx_fee_sat
7013+ - anchors_sat - estimated_fees
7014+ - dust_limit_satoshis,
7015+ )
7016+ } else if counterparty_has_output && !node_0_is_initiator {
7017+ Amount :: from_sat ( initiator_value_to_self_sat - estimated_fees)
7018+ } else if !counterparty_has_output && !node_0_is_initiator {
7019+ Amount :: from_sat ( initiator_value_to_self_sat - estimated_fees - dust_limit_satoshis)
7020+ } else {
7021+ panic ! ( "unexpected case!" ) ;
7022+ } ;
7023+ let outputs = vec ! [ TxOut {
7024+ value: splice_out_max_value + if splice_passes { Amount :: ZERO } else { Amount :: ONE_SAT } ,
7025+ script_pubkey: nodes[ 0 ] . wallet_source. get_change_script( ) . unwrap( ) ,
7026+ } ] ;
7027+
7028+ let ( initiator, acceptor) =
7029+ if node_0_is_initiator { ( & nodes[ 0 ] , & nodes[ 1 ] ) } else { ( & nodes[ 1 ] , & nodes[ 0 ] ) } ;
7030+
7031+ let initiator_details = & initiator. node . list_channels ( ) [ 0 ] ;
7032+ assert_eq ! (
7033+ initiator_details. next_splice_out_maximum_sat,
7034+ splice_out_max_value. to_sat( ) + estimated_fees
7035+ ) ;
7036+
7037+ if splice_passes {
7038+ let contribution = initiate_splice_out ( initiator, acceptor, channel_id, outputs) . unwrap ( ) ;
7039+
7040+ let ( splice_tx, _) = splice_channel ( initiator, acceptor, channel_id, contribution) ;
7041+ mine_transaction ( initiator, & splice_tx) ;
7042+ mine_transaction ( acceptor, & splice_tx) ;
7043+ lock_splice_after_blocks ( initiator, acceptor, ANTI_REORG_DELAY - 1 ) ;
7044+ } else {
7045+ assert ! ( initiate_splice_out( initiator, acceptor, channel_id, outputs) . is_err( ) ) ;
7046+ let splice_out_value =
7047+ splice_out_max_value + Amount :: from_sat ( estimated_fees) + Amount :: ONE_SAT ;
7048+ let splice_out_max_value = splice_out_max_value + Amount :: from_sat ( estimated_fees) ;
7049+ let cannot_be_funded = format ! (
7050+ "Channel {channel_id} cannot be funded: Our \
7051+ splice-out value of {splice_out_value} is greater than the maximum \
7052+ {splice_out_max_value}"
7053+ ) ;
7054+ initiator. logger . assert_log ( "lightning::ln::channel" , cannot_be_funded, 1 ) ;
7055+ }
7056+
7057+ channel_type
7058+ }
7059+
7060+ #[ cfg( test) ]
7061+ fn do_test_0reserve_splice_counterparty_validation (
7062+ splice_passes : bool , counterparty_has_output : bool , node_0_is_initiator : bool ,
7063+ mut config : UserConfig ,
7064+ ) -> ChannelTypeFeatures {
7065+ use crate :: ln:: htlc_reserve_unit_tests:: setup_0reserve_no_outputs_channels;
7066+
7067+ let chanmon_cfgs = create_chanmon_cfgs ( 2 ) ;
7068+ let node_cfgs = create_node_cfgs ( 2 , & chanmon_cfgs) ;
7069+ config. channel_handshake_config . announced_channel_max_inbound_htlc_value_in_flight_percentage =
7070+ 100 ;
7071+ let node_chanmgrs =
7072+ create_node_chanmgrs ( 2 , & node_cfgs, & [ Some ( config. clone ( ) ) , Some ( config. clone ( ) ) ] ) ;
7073+ let nodes = create_network ( 2 , & node_cfgs, & node_chanmgrs) ;
7074+
7075+ let _node_id_0 = nodes[ 0 ] . node . get_our_node_id ( ) ;
7076+ let _node_id_1 = nodes[ 1 ] . node . get_our_node_id ( ) ;
7077+
7078+ let channel_value_sat = 100_000 ;
7079+ // Some dust limit, does not matter
7080+ let dust_limit_satoshis = 546 ;
7081+
7082+ let ( channel_id, _tx) =
7083+ setup_0reserve_no_outputs_channels ( & nodes, channel_value_sat, dust_limit_satoshis) ;
7084+ let details = & nodes[ 0 ] . node . list_channels ( ) [ 0 ] ;
7085+ let channel_type = details. channel_type . clone ( ) . unwrap ( ) ;
7086+
7087+ let feerate = 253 ;
7088+ let spiked_feerate = if channel_type == ChannelTypeFeatures :: only_static_remote_key ( ) {
7089+ feerate * FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE as u32
7090+ } else if channel_type == ChannelTypeFeatures :: anchors_zero_htlc_fee_and_dependencies ( ) {
7091+ feerate
7092+ } else {
7093+ panic ! ( "Unexpected channel type" ) ;
7094+ } ;
7095+ let anchors_sat =
7096+ if channel_type == ChannelTypeFeatures :: anchors_zero_htlc_fee_and_dependencies ( ) {
7097+ ANCHOR_OUTPUT_VALUE_SATOSHI * 2
7098+ } else {
7099+ 0
7100+ } ;
7101+
7102+ let initiator_value_to_self_sat = if counterparty_has_output {
7103+ send_payment ( & nodes[ 0 ] , & [ & nodes[ 1 ] ] , channel_value_sat / 2 * 1_000 ) ;
7104+ channel_value_sat / 2
7105+ } else if !node_0_is_initiator {
7106+ let tx_fee_msat = chan_utils:: commit_tx_fee_sat ( spiked_feerate, 2 , & channel_type) * 1000 ;
7107+ let node_0_details = & nodes[ 0 ] . node . list_channels ( ) [ 0 ] ;
7108+ let outbound_capacity_msat = node_0_details. outbound_capacity_msat ;
7109+ let available_capacity_msat = node_0_details. next_outbound_htlc_limit_msat ;
7110+ assert_eq ! ( outbound_capacity_msat, ( channel_value_sat - anchors_sat) * 1000 ) ;
7111+ assert_eq ! ( available_capacity_msat, outbound_capacity_msat - tx_fee_msat) ;
7112+ send_payment ( & nodes[ 0 ] , & [ & nodes[ 1 ] ] , available_capacity_msat) ;
7113+
7114+ // Make sure node 0 has no output on the commitment at this point
7115+ let node_0_to_local_output_msat = channel_value_sat * 1000
7116+ - available_capacity_msat
7117+ - anchors_sat * 1000
7118+ - chan_utils:: commit_tx_fee_sat ( spiked_feerate, 0 , & channel_type) * 1000 ;
7119+ assert ! ( node_0_to_local_output_msat / 1000 < dust_limit_satoshis) ;
7120+ let commit_tx = & get_local_commitment_txn ! ( nodes[ 0 ] , channel_id) [ 0 ] ;
7121+ assert_eq ! ( commit_tx. output. len( ) , if anchors_sat == 0 { 1 } else { 2 } ) ;
7122+ assert_eq ! (
7123+ commit_tx. output. last( ) . unwrap( ) . value,
7124+ Amount :: from_sat( available_capacity_msat / 1000 )
7125+ ) ;
7126+ if anchors_sat != 0 {
7127+ assert_eq ! ( commit_tx. output[ 0 ] . value, Amount :: from_sat( 330 ) ) ;
7128+ }
7129+
7130+ available_capacity_msat / 1000
7131+ } else {
7132+ channel_value_sat
7133+ } ;
7134+
7135+ let splice_out_value_incl_fees = if counterparty_has_output && node_0_is_initiator {
7136+ let commit_tx_fee_sat = chan_utils:: commit_tx_fee_sat ( spiked_feerate, 1 , & channel_type) ;
7137+ Amount :: from_sat ( initiator_value_to_self_sat - commit_tx_fee_sat - anchors_sat)
7138+ } else if !counterparty_has_output && node_0_is_initiator {
7139+ let commit_tx_fee_sat = chan_utils:: commit_tx_fee_sat ( spiked_feerate, 0 , & channel_type) ;
7140+ Amount :: from_sat (
7141+ initiator_value_to_self_sat - commit_tx_fee_sat - anchors_sat - dust_limit_satoshis,
7142+ )
7143+ } else if counterparty_has_output && !node_0_is_initiator {
7144+ Amount :: from_sat ( initiator_value_to_self_sat)
7145+ } else if !counterparty_has_output && !node_0_is_initiator {
7146+ Amount :: from_sat ( initiator_value_to_self_sat - dust_limit_satoshis)
7147+ } else {
7148+ panic ! ( "unexpected case!" ) ;
7149+ } ;
7150+
7151+ let ( initiator, acceptor) =
7152+ if node_0_is_initiator { ( & nodes[ 0 ] , & nodes[ 1 ] ) } else { ( & nodes[ 1 ] , & nodes[ 0 ] ) } ;
7153+
7154+ let initiator_details = & initiator. node . list_channels ( ) [ 0 ] ;
7155+ assert_eq ! ( initiator_details. next_splice_out_maximum_sat, splice_out_value_incl_fees. to_sat( ) ) ;
7156+
7157+ let funding_contribution_sat =
7158+ -( splice_out_value_incl_fees. to_sat ( ) as i64 ) - if splice_passes { 0 } else { 1 } ;
7159+ let outputs = vec ! [ TxOut {
7160+ // Splice out some dummy amount to get past the initiator's validation,
7161+ // we'll modify the message in-flight.
7162+ value: Amount :: from_sat( 1_000 ) ,
7163+ script_pubkey: nodes[ 0 ] . wallet_source. get_change_script( ) . unwrap( ) ,
7164+ } ] ;
7165+ let _contribution = initiate_splice_out ( initiator, acceptor, channel_id, outputs) . unwrap ( ) ;
7166+
7167+ let node_id_initiator = initiator. node . get_our_node_id ( ) ;
7168+ let node_id_acceptor = acceptor. node . get_our_node_id ( ) ;
7169+
7170+ let stfu_init = get_event_msg ! ( initiator, MessageSendEvent :: SendStfu , node_id_acceptor) ;
7171+ acceptor. node . handle_stfu ( node_id_initiator, & stfu_init) ;
7172+ let stfu_ack = get_event_msg ! ( acceptor, MessageSendEvent :: SendStfu , node_id_initiator) ;
7173+ initiator. node . handle_stfu ( node_id_acceptor, & stfu_ack) ;
7174+
7175+ let mut splice_init =
7176+ get_event_msg ! ( initiator, MessageSendEvent :: SendSpliceInit , node_id_acceptor) ;
7177+ // Make the modification here
7178+ splice_init. funding_contribution_satoshis = funding_contribution_sat;
7179+
7180+ if splice_passes {
7181+ acceptor. node . handle_splice_init ( node_id_initiator, & splice_init) ;
7182+ let _splice_ack =
7183+ get_event_msg ! ( acceptor, MessageSendEvent :: SendSpliceAck , node_id_initiator) ;
7184+ } else {
7185+ acceptor. node . handle_splice_init ( node_id_initiator, & splice_init) ;
7186+ let msg_events = acceptor. node . get_and_clear_pending_msg_events ( ) ;
7187+ assert_eq ! ( msg_events. len( ) , 1 ) ;
7188+ if let MessageSendEvent :: HandleError { action, .. } = & msg_events[ 0 ] {
7189+ assert ! ( matches!( action, msgs:: ErrorAction :: DisconnectPeerWithWarning { .. } ) ) ;
7190+ } else {
7191+ panic ! ( "Expected MessageSendEvent::HandleError" ) ;
7192+ }
7193+ let cannot_splice_out = if u64:: try_from ( funding_contribution_sat. abs ( ) ) . unwrap ( )
7194+ > initiator_value_to_self_sat
7195+ {
7196+ format ! (
7197+ "Got non-closing error: Their contribution candidate {funding_contribution_sat}sat \
7198+ is greater than their total balance in the channel {initiator_value_to_self_sat}sat"
7199+ )
7200+ } else {
7201+ format ! (
7202+ "Got non-closing error: Channel {channel_id} cannot \
7203+ be spliced; Balance exhausted on local commitment"
7204+ )
7205+ } ;
7206+ acceptor. logger . assert_log ( "lightning::ln::channelmanager" , cannot_splice_out, 1 ) ;
7207+ }
7208+
7209+ channel_type
7210+ }
0 commit comments