@@ -816,6 +816,161 @@ fn test_splice_out() {
816816 let _ = send_payment ( & nodes[ 0 ] , & [ & nodes[ 1 ] ] , htlc_limit_msat) ;
817817}
818818
819+ #[ test]
820+ fn test_splice_in_and_out ( ) {
821+ let chanmon_cfgs = create_chanmon_cfgs ( 2 ) ;
822+ let node_cfgs = create_node_cfgs ( 2 , & chanmon_cfgs) ;
823+ let mut config = test_default_channel_config ( ) ;
824+ config. channel_handshake_config . max_inbound_htlc_value_in_flight_percent_of_channel = 100 ;
825+ let node_chanmgrs = create_node_chanmgrs ( 2 , & node_cfgs, & [ None , Some ( config) ] ) ;
826+ let nodes = create_network ( 2 , & node_cfgs, & node_chanmgrs) ;
827+
828+ let initial_channel_value_sat = 100_000 ;
829+ let ( _, _, channel_id, _) =
830+ create_announced_chan_between_nodes_with_value ( & nodes, 0 , 1 , initial_channel_value_sat, 0 ) ;
831+
832+ let _ = send_payment ( & nodes[ 0 ] , & [ & nodes[ 1 ] ] , 100_000 ) ;
833+
834+ let coinbase_tx1 = provide_anchor_reserves ( & nodes) ;
835+ let coinbase_tx2 = provide_anchor_reserves ( & nodes) ;
836+
837+ // Contribute a net negative value, with fees taken from the contributed inputs and the
838+ // remaining value sent to change
839+ let htlc_limit_msat = nodes[ 0 ] . node . list_channels ( ) [ 0 ] . next_outbound_htlc_limit_msat ;
840+ let added_value = Amount :: from_sat ( htlc_limit_msat / 1000 ) ;
841+ let removed_value = added_value * 2 ;
842+ let change_script = ScriptBuf :: new_p2wpkh ( & WPubkeyHash :: all_zeros ( ) ) ;
843+ let fees = Amount :: from_sat ( 383 ) ;
844+
845+ assert ! ( htlc_limit_msat > initial_channel_value_sat / 2 * 1000 ) ;
846+
847+ let initiator_contribution = SpliceContribution :: splice_in_and_out (
848+ added_value,
849+ vec ! [
850+ FundingTxInput :: new_p2wpkh( coinbase_tx1, 0 ) . unwrap( ) ,
851+ FundingTxInput :: new_p2wpkh( coinbase_tx2, 0 ) . unwrap( ) ,
852+ ] ,
853+ vec ! [
854+ TxOut {
855+ value: removed_value / 2 ,
856+ script_pubkey: nodes[ 0 ] . wallet_source. get_change_script( ) . unwrap( ) ,
857+ } ,
858+ TxOut {
859+ value: removed_value / 2 ,
860+ script_pubkey: nodes[ 1 ] . wallet_source. get_change_script( ) . unwrap( ) ,
861+ } ,
862+ ] ,
863+ Some ( change_script. clone ( ) ) ,
864+ ) ;
865+
866+ let splice_tx = splice_channel ( & nodes[ 0 ] , & nodes[ 1 ] , channel_id, initiator_contribution) ;
867+ let expected_change = Amount :: ONE_BTC * 2 - added_value - fees;
868+ assert_eq ! (
869+ splice_tx. output. iter( ) . find( |txout| txout. script_pubkey == change_script) . unwrap( ) . value,
870+ expected_change,
871+ ) ;
872+
873+ mine_transaction ( & nodes[ 0 ] , & splice_tx) ;
874+ mine_transaction ( & nodes[ 1 ] , & splice_tx) ;
875+
876+ let htlc_limit_msat = nodes[ 0 ] . node . list_channels ( ) [ 0 ] . next_outbound_htlc_limit_msat ;
877+ assert ! ( htlc_limit_msat < added_value. to_sat( ) * 1000 ) ;
878+ let _ = send_payment ( & nodes[ 0 ] , & [ & nodes[ 1 ] ] , htlc_limit_msat) ;
879+
880+ lock_splice_after_blocks ( & nodes[ 0 ] , & nodes[ 1 ] , ANTI_REORG_DELAY - 1 ) ;
881+
882+ let htlc_limit_msat = nodes[ 0 ] . node . list_channels ( ) [ 0 ] . next_outbound_htlc_limit_msat ;
883+ assert ! ( htlc_limit_msat < added_value. to_sat( ) * 1000 ) ;
884+ let _ = send_payment ( & nodes[ 0 ] , & [ & nodes[ 1 ] ] , htlc_limit_msat) ;
885+
886+ let coinbase_tx1 = provide_anchor_reserves ( & nodes) ;
887+ let coinbase_tx2 = provide_anchor_reserves ( & nodes) ;
888+
889+ // Contribute a net positive value, with fees taken from the contributed inputs and the
890+ // remaining value sent to change
891+ let added_value = Amount :: from_sat ( initial_channel_value_sat * 2 ) ;
892+ let removed_value = added_value / 2 ;
893+ let change_script = ScriptBuf :: new_p2wpkh ( & WPubkeyHash :: all_zeros ( ) ) ;
894+ let fees = Amount :: from_sat ( 383 ) ;
895+ let initiator_contribution = SpliceContribution :: splice_in_and_out (
896+ added_value,
897+ vec ! [
898+ FundingTxInput :: new_p2wpkh( coinbase_tx1, 0 ) . unwrap( ) ,
899+ FundingTxInput :: new_p2wpkh( coinbase_tx2, 0 ) . unwrap( ) ,
900+ ] ,
901+ vec ! [
902+ TxOut {
903+ value: removed_value / 2 ,
904+ script_pubkey: nodes[ 0 ] . wallet_source. get_change_script( ) . unwrap( ) ,
905+ } ,
906+ TxOut {
907+ value: removed_value / 2 ,
908+ script_pubkey: nodes[ 1 ] . wallet_source. get_change_script( ) . unwrap( ) ,
909+ } ,
910+ ] ,
911+ Some ( change_script. clone ( ) ) ,
912+ ) ;
913+
914+ let splice_tx = splice_channel ( & nodes[ 0 ] , & nodes[ 1 ] , channel_id, initiator_contribution) ;
915+ let expected_change = Amount :: ONE_BTC * 2 - added_value - fees;
916+ assert_eq ! (
917+ splice_tx. output. iter( ) . find( |txout| txout. script_pubkey == change_script) . unwrap( ) . value,
918+ expected_change,
919+ ) ;
920+
921+ mine_transaction ( & nodes[ 0 ] , & splice_tx) ;
922+ mine_transaction ( & nodes[ 1 ] , & splice_tx) ;
923+
924+ let htlc_limit_msat = nodes[ 0 ] . node . list_channels ( ) [ 0 ] . next_outbound_htlc_limit_msat ;
925+ assert_eq ! ( htlc_limit_msat, 0 ) ;
926+
927+ lock_splice_after_blocks ( & nodes[ 0 ] , & nodes[ 1 ] , ANTI_REORG_DELAY - 1 ) ;
928+
929+ let htlc_limit_msat = nodes[ 0 ] . node . list_channels ( ) [ 0 ] . next_outbound_htlc_limit_msat ;
930+ assert ! ( htlc_limit_msat > initial_channel_value_sat / 2 * 1000 ) ;
931+ let _ = send_payment ( & nodes[ 0 ] , & [ & nodes[ 1 ] ] , htlc_limit_msat) ;
932+
933+ let coinbase_tx1 = provide_anchor_reserves ( & nodes) ;
934+ let coinbase_tx2 = provide_anchor_reserves ( & nodes) ;
935+
936+ // Fail adding a net contribution value of zero
937+ let added_value = Amount :: from_sat ( initial_channel_value_sat * 2 ) ;
938+ let removed_value = added_value;
939+ let change_script = ScriptBuf :: new_p2wpkh ( & WPubkeyHash :: all_zeros ( ) ) ;
940+
941+ let initiator_contribution = SpliceContribution :: splice_in_and_out (
942+ added_value,
943+ vec ! [
944+ FundingTxInput :: new_p2wpkh( coinbase_tx1, 0 ) . unwrap( ) ,
945+ FundingTxInput :: new_p2wpkh( coinbase_tx2, 0 ) . unwrap( ) ,
946+ ] ,
947+ vec ! [
948+ TxOut {
949+ value: removed_value / 2 ,
950+ script_pubkey: nodes[ 0 ] . wallet_source. get_change_script( ) . unwrap( ) ,
951+ } ,
952+ TxOut {
953+ value: removed_value / 2 ,
954+ script_pubkey: nodes[ 1 ] . wallet_source. get_change_script( ) . unwrap( ) ,
955+ } ,
956+ ] ,
957+ Some ( change_script) ,
958+ ) ;
959+
960+ assert_eq ! (
961+ nodes[ 0 ] . node. splice_channel(
962+ & channel_id,
963+ & nodes[ 1 ] . node. get_our_node_id( ) ,
964+ initiator_contribution,
965+ FEERATE_FLOOR_SATS_PER_KW ,
966+ None ,
967+ ) ,
968+ Err ( APIError :: APIMisuseError {
969+ err: format!( "Channel {} cannot be spliced; contribution cannot be zero" , channel_id) ,
970+ } ) ,
971+ ) ;
972+ }
973+
819974#[ cfg( test) ]
820975#[ derive( PartialEq ) ]
821976enum SpliceStatus {
0 commit comments