]> git.bitcoin.ninja Git - rust-lightning/commitdiff
Merge pull request #3144 from TheBlueMatt/2024-06-message-flags
authorMatt Corallo <649246+TheBlueMatt@users.noreply.github.com>
Tue, 2 Jul 2024 22:16:59 +0000 (15:16 -0700)
committerGitHub <noreply@github.com>
Tue, 2 Jul 2024 22:16:59 +0000 (15:16 -0700)
(Re-)add handling for `ChannelUpdate::message_flags`

23 files changed:
.github/workflows/build.yml
fuzz/src/bin/gen_target.sh
fuzz/src/bin/msg_splice_init_target.rs [new file with mode: 0644]
fuzz/src/bin/msg_splice_target.rs [deleted file]
fuzz/src/msg_targets/gen_target.sh
fuzz/src/msg_targets/mod.rs
fuzz/src/msg_targets/msg_splice.rs [deleted file]
fuzz/src/msg_targets/msg_splice_init.rs [new file with mode: 0644]
fuzz/src/onion_message.rs
fuzz/targets.h
lightning-background-processor/src/lib.rs
lightning-net-tokio/src/lib.rs
lightning/src/events/mod.rs
lightning/src/ln/channel.rs
lightning/src/ln/channelmanager.rs
lightning/src/ln/functional_test_utils.rs
lightning/src/ln/functional_tests.rs
lightning/src/ln/interactivetxs.rs
lightning/src/ln/msgs.rs
lightning/src/ln/peer_handler.rs
lightning/src/ln/wire.rs
lightning/src/onion_message/messenger.rs
lightning/src/util/test_utils.rs

index 1a7c7f85cca0b54964a9bfd28110185547df80d7..90ecd4431c7aac8cfad73f5d18d39bc5822473df 100644 (file)
@@ -247,3 +247,19 @@ jobs:
           rustup component add rustfmt
       - name: Run rustfmt checks
         run: ci/rustfmt.sh
+
+  incremental-mutants:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          fetch-depth: 0
+      - name: Relative diff
+        run: |
+          git branch -av
+          git diff origin/main.. | tee git.diff
+      - uses: Swatinem/rust-cache@v2
+      - name: Mutants
+        run: |
+          cargo install cargo-mutants
+          cargo mutants --no-shuffle -j 2 -vV --in-diff git.diff
index f124c1e0ef03bd710417cbebf0b770f81df8094e..717248aa5758165ad016d980c72cd1fd4edb3844 100755 (executable)
@@ -74,6 +74,6 @@ GEN_TEST msg_tx_abort msg_targets::
 
 GEN_TEST msg_stfu msg_targets::
 
-GEN_TEST msg_splice msg_targets::
+GEN_TEST msg_splice_init msg_targets::
 GEN_TEST msg_splice_ack msg_targets::
 GEN_TEST msg_splice_locked msg_targets::
diff --git a/fuzz/src/bin/msg_splice_init_target.rs b/fuzz/src/bin/msg_splice_init_target.rs
new file mode 100644 (file)
index 0000000..83df645
--- /dev/null
@@ -0,0 +1,120 @@
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+// This file is auto-generated by gen_target.sh based on target_template.txt
+// To modify it, modify target_template.txt and run gen_target.sh instead.
+
+#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
+#![cfg_attr(rustfmt, rustfmt_skip)]
+
+#[cfg(not(fuzzing))]
+compile_error!("Fuzz targets need cfg=fuzzing");
+
+#[cfg(not(hashes_fuzz))]
+compile_error!("Fuzz targets need cfg=hashes_fuzz");
+
+#[cfg(not(secp256k1_fuzz))]
+compile_error!("Fuzz targets need cfg=secp256k1_fuzz");
+
+extern crate lightning_fuzz;
+use lightning_fuzz::msg_targets::msg_splice_init::*;
+
+#[cfg(feature = "afl")]
+#[macro_use] extern crate afl;
+#[cfg(feature = "afl")]
+fn main() {
+       fuzz!(|data| {
+               msg_splice_init_run(data.as_ptr(), data.len());
+       });
+}
+
+#[cfg(feature = "honggfuzz")]
+#[macro_use] extern crate honggfuzz;
+#[cfg(feature = "honggfuzz")]
+fn main() {
+       loop {
+               fuzz!(|data| {
+                       msg_splice_init_run(data.as_ptr(), data.len());
+               });
+       }
+}
+
+#[cfg(feature = "libfuzzer_fuzz")]
+#[macro_use] extern crate libfuzzer_sys;
+#[cfg(feature = "libfuzzer_fuzz")]
+fuzz_target!(|data: &[u8]| {
+       msg_splice_init_run(data.as_ptr(), data.len());
+});
+
+#[cfg(feature = "stdin_fuzz")]
+fn main() {
+       use std::io::Read;
+
+       let mut data = Vec::with_capacity(8192);
+       std::io::stdin().read_to_end(&mut data).unwrap();
+       msg_splice_init_run(data.as_ptr(), data.len());
+}
+
+#[test]
+fn run_test_cases() {
+       use std::fs;
+       use std::io::Read;
+       use lightning_fuzz::utils::test_logger::StringBuffer;
+
+       use std::sync::{atomic, Arc};
+       {
+               let data: Vec<u8> = vec![0];
+               msg_splice_init_run(data.as_ptr(), data.len());
+       }
+       let mut threads = Vec::new();
+       let threads_running = Arc::new(atomic::AtomicUsize::new(0));
+       if let Ok(tests) = fs::read_dir("test_cases/msg_splice_init") {
+               for test in tests {
+                       let mut data: Vec<u8> = Vec::new();
+                       let path = test.unwrap().path();
+                       fs::File::open(&path).unwrap().read_to_end(&mut data).unwrap();
+                       threads_running.fetch_add(1, atomic::Ordering::AcqRel);
+
+                       let thread_count_ref = Arc::clone(&threads_running);
+                       let main_thread_ref = std::thread::current();
+                       threads.push((path.file_name().unwrap().to_str().unwrap().to_string(),
+                               std::thread::spawn(move || {
+                                       let string_logger = StringBuffer::new();
+
+                                       let panic_logger = string_logger.clone();
+                                       let res = if ::std::panic::catch_unwind(move || {
+                                               msg_splice_init_test(&data, panic_logger);
+                                       }).is_err() {
+                                               Some(string_logger.into_string())
+                                       } else { None };
+                                       thread_count_ref.fetch_sub(1, atomic::Ordering::AcqRel);
+                                       main_thread_ref.unpark();
+                                       res
+                               })
+                       ));
+                       while threads_running.load(atomic::Ordering::Acquire) > 32 {
+                               std::thread::park();
+                       }
+               }
+       }
+       let mut failed_outputs = Vec::new();
+       for (test, thread) in threads.drain(..) {
+               if let Some(output) = thread.join().unwrap() {
+                       println!("\nOutput of {}:\n{}\n", test, output);
+                       failed_outputs.push(test);
+               }
+       }
+       if !failed_outputs.is_empty() {
+               println!("Test cases which failed: ");
+               for case in failed_outputs {
+                       println!("{}", case);
+               }
+               panic!();
+       }
+}
diff --git a/fuzz/src/bin/msg_splice_target.rs b/fuzz/src/bin/msg_splice_target.rs
deleted file mode 100644 (file)
index a88b189..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-// This file is Copyright its original authors, visible in version control
-// history.
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-// This file is auto-generated by gen_target.sh based on target_template.txt
-// To modify it, modify target_template.txt and run gen_target.sh instead.
-
-#![cfg_attr(feature = "libfuzzer_fuzz", no_main)]
-#![cfg_attr(rustfmt, rustfmt_skip)]
-
-#[cfg(not(fuzzing))]
-compile_error!("Fuzz targets need cfg=fuzzing");
-
-#[cfg(not(hashes_fuzz))]
-compile_error!("Fuzz targets need cfg=hashes_fuzz");
-
-#[cfg(not(secp256k1_fuzz))]
-compile_error!("Fuzz targets need cfg=secp256k1_fuzz");
-
-extern crate lightning_fuzz;
-use lightning_fuzz::msg_targets::msg_splice::*;
-
-#[cfg(feature = "afl")]
-#[macro_use] extern crate afl;
-#[cfg(feature = "afl")]
-fn main() {
-       fuzz!(|data| {
-               msg_splice_run(data.as_ptr(), data.len());
-       });
-}
-
-#[cfg(feature = "honggfuzz")]
-#[macro_use] extern crate honggfuzz;
-#[cfg(feature = "honggfuzz")]
-fn main() {
-       loop {
-               fuzz!(|data| {
-                       msg_splice_run(data.as_ptr(), data.len());
-               });
-       }
-}
-
-#[cfg(feature = "libfuzzer_fuzz")]
-#[macro_use] extern crate libfuzzer_sys;
-#[cfg(feature = "libfuzzer_fuzz")]
-fuzz_target!(|data: &[u8]| {
-       msg_splice_run(data.as_ptr(), data.len());
-});
-
-#[cfg(feature = "stdin_fuzz")]
-fn main() {
-       use std::io::Read;
-
-       let mut data = Vec::with_capacity(8192);
-       std::io::stdin().read_to_end(&mut data).unwrap();
-       msg_splice_run(data.as_ptr(), data.len());
-}
-
-#[test]
-fn run_test_cases() {
-       use std::fs;
-       use std::io::Read;
-       use lightning_fuzz::utils::test_logger::StringBuffer;
-
-       use std::sync::{atomic, Arc};
-       {
-               let data: Vec<u8> = vec![0];
-               msg_splice_run(data.as_ptr(), data.len());
-       }
-       let mut threads = Vec::new();
-       let threads_running = Arc::new(atomic::AtomicUsize::new(0));
-       if let Ok(tests) = fs::read_dir("test_cases/msg_splice") {
-               for test in tests {
-                       let mut data: Vec<u8> = Vec::new();
-                       let path = test.unwrap().path();
-                       fs::File::open(&path).unwrap().read_to_end(&mut data).unwrap();
-                       threads_running.fetch_add(1, atomic::Ordering::AcqRel);
-
-                       let thread_count_ref = Arc::clone(&threads_running);
-                       let main_thread_ref = std::thread::current();
-                       threads.push((path.file_name().unwrap().to_str().unwrap().to_string(),
-                               std::thread::spawn(move || {
-                                       let string_logger = StringBuffer::new();
-
-                                       let panic_logger = string_logger.clone();
-                                       let res = if ::std::panic::catch_unwind(move || {
-                                               msg_splice_test(&data, panic_logger);
-                                       }).is_err() {
-                                               Some(string_logger.into_string())
-                                       } else { None };
-                                       thread_count_ref.fetch_sub(1, atomic::Ordering::AcqRel);
-                                       main_thread_ref.unpark();
-                                       res
-                               })
-                       ));
-                       while threads_running.load(atomic::Ordering::Acquire) > 32 {
-                               std::thread::park();
-                       }
-               }
-       }
-       let mut failed_outputs = Vec::new();
-       for (test, thread) in threads.drain(..) {
-               if let Some(output) = thread.join().unwrap() {
-                       println!("\nOutput of {}:\n{}\n", test, output);
-                       failed_outputs.push(test);
-               }
-       }
-       if !failed_outputs.is_empty() {
-               println!("Test cases which failed: ");
-               for case in failed_outputs {
-                       println!("{}", case);
-               }
-               panic!();
-       }
-}
index 71c3d895ba2fabfcea029abc1175ee8e42eec974..c8cc45926a1a885322220ff2669cff4d15666706 100755 (executable)
@@ -67,6 +67,6 @@ GEN_TEST lightning::ln::msgs::TxAbort test_msg_simple ""
 
 GEN_TEST lightning::ln::msgs::Stfu test_msg_simple ""
 
-GEN_TEST lightning::ln::msgs::Splice test_msg_simple ""
+GEN_TEST lightning::ln::msgs::SpliceInit test_msg_simple ""
 GEN_TEST lightning::ln::msgs::SpliceAck test_msg_simple ""
 GEN_TEST lightning::ln::msgs::SpliceLocked test_msg_simple ""
index 16721fa627a763683a279b031da36c52e397c937..6f5f8120ca0210cc6622ef2f0459fc7c3594d428 100644 (file)
@@ -43,6 +43,6 @@ pub mod msg_tx_init_rbf;
 pub mod msg_tx_ack_rbf;
 pub mod msg_tx_abort;
 pub mod msg_stfu;
-pub mod msg_splice;
+pub mod msg_splice_init;
 pub mod msg_splice_ack;
 pub mod msg_splice_locked;
diff --git a/fuzz/src/msg_targets/msg_splice.rs b/fuzz/src/msg_targets/msg_splice.rs
deleted file mode 100644 (file)
index f203da0..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-// This file is Copyright its original authors, visible in version control
-// history.
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-// This file is auto-generated by gen_target.sh based on msg_target_template.txt
-// To modify it, modify msg_target_template.txt and run gen_target.sh instead.
-
-#![cfg_attr(rustfmt, rustfmt_skip)]
-
-use crate::msg_targets::utils::VecWriter;
-use crate::utils::test_logger;
-
-#[inline]
-pub fn msg_splice_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
-       test_msg_simple!(lightning::ln::msgs::Splice, data);
-}
-
-#[no_mangle]
-pub extern "C" fn msg_splice_run(data: *const u8, datalen: usize) {
-       let data = unsafe { std::slice::from_raw_parts(data, datalen) };
-       test_msg_simple!(lightning::ln::msgs::Splice, data);
-}
diff --git a/fuzz/src/msg_targets/msg_splice_init.rs b/fuzz/src/msg_targets/msg_splice_init.rs
new file mode 100644 (file)
index 0000000..d2e3829
--- /dev/null
@@ -0,0 +1,27 @@
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+// This file is auto-generated by gen_target.sh based on msg_target_template.txt
+// To modify it, modify msg_target_template.txt and run gen_target.sh instead.
+
+#![cfg_attr(rustfmt, rustfmt_skip)]
+
+use crate::msg_targets::utils::VecWriter;
+use crate::utils::test_logger;
+
+#[inline]
+pub fn msg_splice_init_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
+       test_msg_simple!(lightning::ln::msgs::SpliceInit, data);
+}
+
+#[no_mangle]
+pub extern "C" fn msg_splice_init_run(data: *const u8, datalen: usize) {
+       let data = unsafe { std::slice::from_raw_parts(data, datalen) };
+       test_msg_simple!(lightning::ln::msgs::SpliceInit, data);
+}
index ba76815af59cb6b88226ca45c5fede1e38b624f7..e60d13d57a90aea5a18c13454a0f432c21fce3cb 100644 (file)
@@ -13,7 +13,7 @@ use lightning::ln::script::ShutdownScript;
 use lightning::offers::invoice::UnsignedBolt12Invoice;
 use lightning::offers::invoice_request::UnsignedInvoiceRequest;
 use lightning::onion_message::async_payments::{
-       AsyncPaymentsMessage, AsyncPaymentsMessageHandler, HeldHtlcAvailable, ReleaseHeldHtlc,
+       AsyncPaymentsMessageHandler, HeldHtlcAvailable, ReleaseHeldHtlc,
 };
 use lightning::onion_message::messenger::{
        CustomOnionMessageHandler, Destination, MessageRouter, OnionMessagePath, OnionMessenger,
@@ -114,9 +114,14 @@ struct TestAsyncPaymentsMessageHandler {}
 
 impl AsyncPaymentsMessageHandler for TestAsyncPaymentsMessageHandler {
        fn held_htlc_available(
-               &self, _message: HeldHtlcAvailable, _responder: Option<Responder>,
+               &self, message: HeldHtlcAvailable, responder: Option<Responder>,
        ) -> ResponseInstruction<ReleaseHeldHtlc> {
-               ResponseInstruction::NoResponse
+               let responder = match responder {
+                       Some(resp) => resp,
+                       None => return ResponseInstruction::NoResponse,
+               };
+               responder
+                       .respond(ReleaseHeldHtlc { payment_release_secret: message.payment_release_secret })
        }
        fn release_held_htlc(&self, _message: ReleaseHeldHtlc) {}
 }
index d8b755c1a7e5a3a7fa9cdb5732b852fd3452404c..b362477a12e0fe0bb9f04bba704f41e7d8c164a3 100644 (file)
@@ -59,6 +59,6 @@ void msg_tx_init_rbf_run(const unsigned char* data, size_t data_len);
 void msg_tx_ack_rbf_run(const unsigned char* data, size_t data_len);
 void msg_tx_abort_run(const unsigned char* data, size_t data_len);
 void msg_stfu_run(const unsigned char* data, size_t data_len);
-void msg_splice_run(const unsigned char* data, size_t data_len);
+void msg_splice_init_run(const unsigned char* data, size_t data_len);
 void msg_splice_ack_run(const unsigned char* data, size_t data_len);
 void msg_splice_locked_run(const unsigned char* data, size_t data_len);
index aae64e981bb54271d0279c26db666bdee77f4d10..a355e2b0985471337a4552f86a6857446942e95c 100644 (file)
@@ -996,7 +996,7 @@ mod tests {
        type PGS = Arc<P2PGossipSync<Arc<NetworkGraph<Arc<test_utils::TestLogger>>>, Arc<test_utils::TestChainSource>, Arc<test_utils::TestLogger>>>;
        type RGS = Arc<RapidGossipSync<Arc<NetworkGraph<Arc<test_utils::TestLogger>>>, Arc<test_utils::TestLogger>>>;
 
-       type OM = OnionMessenger<Arc<KeysManager>, Arc<KeysManager>, Arc<test_utils::TestLogger>, Arc<ChannelManager>, Arc<DefaultMessageRouter<Arc<NetworkGraph<Arc<test_utils::TestLogger>>>, Arc<test_utils::TestLogger>, Arc<KeysManager>>>, IgnoringMessageHandler, IgnoringMessageHandler, IgnoringMessageHandler>;
+       type OM = OnionMessenger<Arc<KeysManager>, Arc<KeysManager>, Arc<test_utils::TestLogger>, Arc<ChannelManager>, Arc<DefaultMessageRouter<Arc<NetworkGraph<Arc<test_utils::TestLogger>>>, Arc<test_utils::TestLogger>, Arc<KeysManager>>>, IgnoringMessageHandler, Arc<ChannelManager>, IgnoringMessageHandler>;
 
        struct Node {
                node: Arc<ChannelManager>,
@@ -1291,7 +1291,7 @@ mod tests {
                        let best_block = BestBlock::from_network(network);
                        let params = ChainParameters { network, best_block };
                        let manager = Arc::new(ChannelManager::new(fee_estimator.clone(), chain_monitor.clone(), tx_broadcaster.clone(), router.clone(), logger.clone(), keys_manager.clone(), keys_manager.clone(), keys_manager.clone(), UserConfig::default(), params, genesis_block.header.time));
-                       let messenger = Arc::new(OnionMessenger::new(keys_manager.clone(), keys_manager.clone(), logger.clone(), manager.clone(), msg_router.clone(), IgnoringMessageHandler {}, IgnoringMessageHandler {}, IgnoringMessageHandler {}));
+                       let messenger = Arc::new(OnionMessenger::new(keys_manager.clone(), keys_manager.clone(), logger.clone(), manager.clone(), msg_router.clone(), IgnoringMessageHandler {}, manager.clone(), IgnoringMessageHandler {}));
                        let wallet = Arc::new(TestWallet {});
                        let sweeper = Arc::new(OutputSweeper::new(best_block, Arc::clone(&tx_broadcaster), Arc::clone(&fee_estimator),
                                None::<Arc<dyn Filter + Sync + Send>>, Arc::clone(&keys_manager), wallet, Arc::clone(&kv_store), Arc::clone(&logger)));
index 6d001ca67fd5e1ec44d0307a198509d83cf2538a..5e926ae95f56e67340a3740a29052da4e6525936 100644 (file)
@@ -625,7 +625,7 @@ mod tests {
                fn handle_accept_channel_v2(&self, _their_node_id: &PublicKey, _msg: &AcceptChannelV2) {}
                fn handle_stfu(&self, _their_node_id: &PublicKey, _msg: &Stfu) {}
                #[cfg(splicing)]
-               fn handle_splice(&self, _their_node_id: &PublicKey, _msg: &Splice) {}
+               fn handle_splice_init(&self, _their_node_id: &PublicKey, _msg: &SpliceInit) {}
                #[cfg(splicing)]
                fn handle_splice_ack(&self, _their_node_id: &PublicKey, _msg: &SpliceAck) {}
                #[cfg(splicing)]
index 887851864c97e84f62a7502673cd37e2259d316a..e59a3d1850139960d46cbd30031a2087bea79ee0 100644 (file)
@@ -2059,12 +2059,12 @@ pub enum MessageSendEvent {
                /// The message which should be sent.
                msg: msgs::Stfu,
        },
-       /// Used to indicate that a splice message should be sent to the peer with the given node id.
-       SendSplice {
+       /// Used to indicate that a splice_init message should be sent to the peer with the given node id.
+       SendSpliceInit {
                /// The node_id of the node which should receive this message
                node_id: PublicKey,
                /// The message which should be sent.
-               msg: msgs::Splice,
+               msg: msgs::SpliceInit,
        },
        /// Used to indicate that a splice_ack message should be sent to the peer with the given node id.
        SendSpliceAck {
index 5db076931ab83e8abc208bea708fee59ae4b70e5..4de551b72bfdf331d4ee64b15bbe8b69ee95d8bf 100644 (file)
@@ -2768,7 +2768,7 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider  {
                        feerate_per_kw = cmp::max(feerate_per_kw, feerate);
                }
                let feerate_plus_quarter = feerate_per_kw.checked_mul(1250).map(|v| v / 1000);
-               cmp::max(feerate_per_kw + 2530, feerate_plus_quarter.unwrap_or(u32::max_value()))
+               cmp::max(feerate_per_kw.saturating_add(2530), feerate_plus_quarter.unwrap_or(u32::MAX))
        }
 
        /// Get forwarding information for the counterparty.
@@ -7272,6 +7272,7 @@ impl<SP: Deref> Channel<SP> where
                                        channel_id: self.context.channel_id,
                                        signature,
                                        htlc_signatures,
+                                       batch: None,
                                        #[cfg(taproot)]
                                        partial_signature_with_nonce: None,
                                }, (counterparty_commitment_txid, commitment_stats.htlcs_included)))
index 86cf4dbb81554a56419bda56d5d98c857cdcfb8b..807dce7cd4536c2307f74df5190445549a0ebdac 100644 (file)
@@ -66,6 +66,7 @@ use crate::offers::invoice_request::{DerivedPayerId, InvoiceRequestBuilder};
 use crate::offers::offer::{Offer, OfferBuilder};
 use crate::offers::parse::Bolt12SemanticError;
 use crate::offers::refund::{Refund, RefundBuilder};
+use crate::onion_message::async_payments::{AsyncPaymentsMessage, HeldHtlcAvailable, ReleaseHeldHtlc, AsyncPaymentsMessageHandler};
 use crate::onion_message::messenger::{new_pending_onion_message, Destination, MessageRouter, PendingOnionMessage, Responder, ResponseInstruction};
 use crate::onion_message::offers::{OffersMessage, OffersMessageHandler};
 use crate::sign::{EntropySource, NodeSigner, Recipient, SignerProvider};
@@ -9693,7 +9694,7 @@ where
        }
 
        #[cfg(splicing)]
-       fn handle_splice(&self, counterparty_node_id: &PublicKey, msg: &msgs::Splice) {
+       fn handle_splice_init(&self, counterparty_node_id: &PublicKey, msg: &msgs::SpliceInit) {
                let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close(
                        "Splicing not supported".to_owned(),
                         msg.channel_id.clone())), *counterparty_node_id);
@@ -9900,7 +9901,7 @@ where
                                                // Quiescence
                                                &events::MessageSendEvent::SendStfu { .. } => false,
                                                // Splicing
-                                               &events::MessageSendEvent::SendSplice { .. } => false,
+                                               &events::MessageSendEvent::SendSpliceInit { .. } => false,
                                                &events::MessageSendEvent::SendSpliceAck { .. } => false,
                                                &events::MessageSendEvent::SendSpliceLocked { .. } => false,
                                                // Interactive Transaction Construction
@@ -10401,6 +10402,31 @@ where
        }
 }
 
+impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>
+AsyncPaymentsMessageHandler for ChannelManager<M, T, ES, NS, SP, F, R, L>
+where
+       M::Target: chain::Watch<<SP::Target as SignerProvider>::EcdsaSigner>,
+       T::Target: BroadcasterInterface,
+       ES::Target: EntropySource,
+       NS::Target: NodeSigner,
+       SP::Target: SignerProvider,
+       F::Target: FeeEstimator,
+       R::Target: Router,
+       L::Target: Logger,
+{
+       fn held_htlc_available(
+               &self, _message: HeldHtlcAvailable, _responder: Option<Responder>
+       ) -> ResponseInstruction<ReleaseHeldHtlc> {
+               ResponseInstruction::NoResponse
+       }
+
+       fn release_held_htlc(&self, _message: ReleaseHeldHtlc) {}
+
+       fn release_pending_messages(&self) -> Vec<PendingOnionMessage<AsyncPaymentsMessage>> {
+               Vec::new()
+       }
+}
+
 impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>
 NodeIdLookUp for ChannelManager<M, T, ES, NS, SP, F, R, L>
 where
index aad61817712b006141f6486adb84e322fab18cfb..c7d2edf51f85e3c83bbcbf804e28e83b135720d3 100644 (file)
@@ -422,7 +422,7 @@ type TestOnionMessenger<'chan_man, 'node_cfg, 'chan_mon_cfg> = OnionMessenger<
        &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>,
        &'node_cfg test_utils::TestMessageRouter<'chan_mon_cfg>,
        &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>,
-       IgnoringMessageHandler,
+       &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>,
        IgnoringMessageHandler,
 >;
 
@@ -915,7 +915,7 @@ pub fn remove_first_msg_event_to_node(msg_node_id: &PublicKey, msg_events: &mut
                MessageSendEvent::SendStfu { node_id, .. } => {
                        node_id == msg_node_id
                },
-               MessageSendEvent::SendSplice { node_id, .. } => {
+               MessageSendEvent::SendSpliceInit { node_id, .. } => {
                        node_id == msg_node_id
                },
                MessageSendEvent::SendSpliceAck { node_id, .. } => {
@@ -3259,7 +3259,7 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(node_count: usize, cfgs: &'b Vec<NodeC
                let dedicated_entropy = DedicatedEntropy(RandomBytes::new([i as u8; 32]));
                let onion_messenger = OnionMessenger::new(
                        dedicated_entropy, cfgs[i].keys_manager, cfgs[i].logger, &chan_mgrs[i],
-                       &cfgs[i].message_router, &chan_mgrs[i], IgnoringMessageHandler {}, IgnoringMessageHandler {},
+                       &cfgs[i].message_router, &chan_mgrs[i], &chan_mgrs[i], IgnoringMessageHandler {},
                );
                let gossip_sync = P2PGossipSync::new(cfgs[i].network_graph.as_ref(), None, cfgs[i].logger);
                let wallet_source = Arc::new(test_utils::TestWalletSource::new(SecretKey::from_slice(&[i as u8 + 1; 32]).unwrap()));
index 6271f73000aa840f8c051929cdf6b3c268c473d5..a01388f20158a65317027bce11f47b51c9108478 100644 (file)
@@ -783,6 +783,7 @@ fn test_update_fee_that_funder_cannot_afford() {
                channel_id: chan.2,
                signature: res.0,
                htlc_signatures: res.1,
+               batch: None,
                #[cfg(taproot)]
                partial_signature_with_nonce: None,
        };
@@ -1532,6 +1533,7 @@ fn test_fee_spike_violation_fails_htlc() {
                channel_id: chan.2,
                signature: res.0,
                htlc_signatures: res.1,
+               batch: None,
                #[cfg(taproot)]
                partial_signature_with_nonce: None,
        };
index 17c6990508610c196574913171300e13003acb86..b6ed64aa8182d855022c08ec881a42668c36f654 100644 (file)
@@ -9,17 +9,14 @@
 
 use crate::io_extras::sink;
 use crate::prelude::*;
-use core::ops::Deref;
 
+use bitcoin::absolute::LockTime as AbsoluteLockTime;
 use bitcoin::amount::Amount;
 use bitcoin::blockdata::constants::WITNESS_SCALE_FACTOR;
 use bitcoin::consensus::Encodable;
 use bitcoin::policy::MAX_STANDARD_TX_WEIGHT;
 use bitcoin::transaction::Version;
-use bitcoin::{
-       absolute::LockTime as AbsoluteLockTime, OutPoint, ScriptBuf, Sequence, Transaction, TxIn,
-       TxOut, Weight,
-};
+use bitcoin::{OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Weight};
 
 use crate::chain::chaininterface::fee_for_weight;
 use crate::events::bump_transaction::{BASE_INPUT_WEIGHT, EMPTY_SCRIPT_SIG_WEIGHT};
@@ -30,6 +27,8 @@ use crate::ln::types::ChannelId;
 use crate::sign::{EntropySource, P2TR_KEY_PATH_WITNESS_WEIGHT, P2WPKH_WITNESS_WEIGHT};
 use crate::util::ser::TransactionU16LenLimited;
 
+use core::ops::Deref;
+
 /// The number of received `tx_add_input` messages during a negotiation at which point the
 /// negotiation MUST be failed.
 const MAX_RECEIVED_TX_ADD_INPUT_COUNT: u16 = 4096;
@@ -99,19 +98,14 @@ pub(crate) enum AbortReason {
        InsufficientFees,
        OutputsValueExceedsInputsValue,
        InvalidTx,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub(crate) struct InteractiveTxInput {
-       serial_id: SerialId,
-       input: TxIn,
-       prev_output: TxOut,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub(crate) struct InteractiveTxOutput {
-       serial_id: SerialId,
-       tx_out: TxOut,
+       /// No funding (shared) output found.
+       MissingFundingOutput,
+       /// More than one funding (shared) output found.
+       DuplicateFundingOutput,
+       /// The intended local part of the funding output is higher than the actual shared funding output,
+       /// if funding output is provided by the peer this is an interop error,
+       /// if provided by the same node than internal input consistency error.
+       InvalidLowFundingOutputValue,
 }
 
 #[derive(Debug, Clone, PartialEq, Eq)]
@@ -135,18 +129,12 @@ impl ConstructedTransaction {
                let local_inputs_value_satoshis = context
                        .inputs
                        .iter()
-                       .filter(|(serial_id, _)| {
-                               !is_serial_id_valid_for_counterparty(context.holder_is_initiator, serial_id)
-                       })
-                       .fold(0u64, |value, (_, input)| value.saturating_add(input.prev_output.value.to_sat()));
+                       .fold(0u64, |value, (_, input)| value.saturating_add(input.local_value()));
 
                let local_outputs_value_satoshis = context
                        .outputs
                        .iter()
-                       .filter(|(serial_id, _)| {
-                               !is_serial_id_valid_for_counterparty(context.holder_is_initiator, serial_id)
-                       })
-                       .fold(0u64, |value, (_, output)| value.saturating_add(output.tx_out.value.to_sat()));
+                       .fold(0u64, |value, (_, output)| value.saturating_add(output.local_value()));
 
                Self {
                        holder_is_initiator: context.holder_is_initiator,
@@ -165,18 +153,12 @@ impl ConstructedTransaction {
        }
 
        pub fn weight(&self) -> Weight {
-               let inputs_weight = self.inputs.iter().fold(
-                       Weight::from_wu(0),
-                       |weight, InteractiveTxInput { prev_output, .. }| {
-                               weight.checked_add(estimate_input_weight(prev_output)).unwrap_or(Weight::MAX)
-                       },
-               );
-               let outputs_weight = self.outputs.iter().fold(
-                       Weight::from_wu(0),
-                       |weight, InteractiveTxOutput { tx_out, .. }| {
-                               weight.checked_add(get_output_weight(&tx_out.script_pubkey)).unwrap_or(Weight::MAX)
-                       },
-               );
+               let inputs_weight = self.inputs.iter().fold(Weight::from_wu(0), |weight, input| {
+                       weight.checked_add(estimate_input_weight(input.prev_output())).unwrap_or(Weight::MAX)
+               });
+               let outputs_weight = self.outputs.iter().fold(Weight::from_wu(0), |weight, output| {
+                       weight.checked_add(get_output_weight(&output.script_pubkey())).unwrap_or(Weight::MAX)
+               });
                Weight::from_wu(TX_COMMON_FIELDS_WEIGHT)
                        .checked_add(inputs_weight)
                        .and_then(|weight| weight.checked_add(outputs_weight))
@@ -187,13 +169,12 @@ impl ConstructedTransaction {
                // Inputs and outputs must be sorted by serial_id
                let ConstructedTransaction { mut inputs, mut outputs, .. } = self;
 
-               inputs.sort_unstable_by_key(|InteractiveTxInput { serial_id, .. }| *serial_id);
-               outputs.sort_unstable_by_key(|InteractiveTxOutput { serial_id, .. }| *serial_id);
+               inputs.sort_unstable_by_key(|input| input.serial_id());
+               outputs.sort_unstable_by_key(|output| output.serial_id);
 
-               let input: Vec<TxIn> =
-                       inputs.into_iter().map(|InteractiveTxInput { input, .. }| input).collect();
+               let input: Vec<TxIn> = inputs.into_iter().map(|input| input.txin().clone()).collect();
                let output: Vec<TxOut> =
-                       outputs.into_iter().map(|InteractiveTxOutput { tx_out, .. }| tx_out).collect();
+                       outputs.into_iter().map(|output| output.tx_out().clone()).collect();
 
                Transaction { version: Version::TWO, lock_time: self.lock_time, input, output }
        }
@@ -205,9 +186,25 @@ struct NegotiationContext {
        received_tx_add_input_count: u16,
        received_tx_add_output_count: u16,
        inputs: HashMap<SerialId, InteractiveTxInput>,
+       /// The output script intended to be the new funding output script.
+       /// The script pubkey is used to determine which output is the funding output.
+       /// When an output with the same script pubkey is added by any of the nodes, it will be
+       /// treated as the shared output.
+       /// The value is the holder's intended contribution to the shared funding output.
+       /// The rest is the counterparty's contribution.
+       /// When the funding output is added (recognized by its output script pubkey), it will be marked
+       /// as shared, and split between the peers according to the local value.
+       /// If the local value is found to be larger than the actual funding output, an error is generated.
+       expected_shared_funding_output: (ScriptBuf, u64),
+       /// The actual new funding output, set only after the output has actually been added.
+       /// NOTE: this output is also included in `outputs`.
+       actual_new_funding_output: Option<SharedOwnedOutput>,
        prevtx_outpoints: HashSet<OutPoint>,
+       /// The outputs added so far.
        outputs: HashMap<SerialId, InteractiveTxOutput>,
+       /// The locktime of the funding transaction.
        tx_locktime: AbsoluteLockTime,
+       /// The fee rate used for the transaction
        feerate_sat_per_kw: u32,
 }
 
@@ -236,26 +233,51 @@ fn is_serial_id_valid_for_counterparty(holder_is_initiator: bool, serial_id: &Se
 }
 
 impl NegotiationContext {
+       fn new(
+               holder_is_initiator: bool, expected_shared_funding_output: (ScriptBuf, u64),
+               tx_locktime: AbsoluteLockTime, feerate_sat_per_kw: u32,
+       ) -> Self {
+               NegotiationContext {
+                       holder_is_initiator,
+                       received_tx_add_input_count: 0,
+                       received_tx_add_output_count: 0,
+                       inputs: new_hash_map(),
+                       expected_shared_funding_output,
+                       actual_new_funding_output: None,
+                       prevtx_outpoints: new_hash_set(),
+                       outputs: new_hash_map(),
+                       tx_locktime,
+                       feerate_sat_per_kw,
+               }
+       }
+
+       fn set_actual_new_funding_output(
+               &mut self, tx_out: TxOut,
+       ) -> Result<SharedOwnedOutput, AbortReason> {
+               if self.actual_new_funding_output.is_some() {
+                       return Err(AbortReason::DuplicateFundingOutput);
+               }
+               let value = tx_out.value.to_sat();
+               let local_owned = self.expected_shared_funding_output.1;
+               // Sanity check
+               if local_owned > value {
+                       return Err(AbortReason::InvalidLowFundingOutputValue);
+               }
+               let shared_output = SharedOwnedOutput::new(tx_out, local_owned);
+               self.actual_new_funding_output = Some(shared_output.clone());
+               Ok(shared_output)
+       }
+
        fn is_serial_id_valid_for_counterparty(&self, serial_id: &SerialId) -> bool {
                is_serial_id_valid_for_counterparty(self.holder_is_initiator, serial_id)
        }
 
        fn remote_inputs_value(&self) -> u64 {
-               self.inputs
-                       .iter()
-                       .filter(|(serial_id, _)| self.is_serial_id_valid_for_counterparty(serial_id))
-                       .fold(0u64, |acc, (_, InteractiveTxInput { prev_output, .. })| {
-                               acc.saturating_add(prev_output.value.to_sat())
-                       })
+               self.inputs.iter().fold(0u64, |acc, (_, input)| acc.saturating_add(input.remote_value()))
        }
 
        fn remote_outputs_value(&self) -> u64 {
-               self.outputs
-                       .iter()
-                       .filter(|(serial_id, _)| self.is_serial_id_valid_for_counterparty(serial_id))
-                       .fold(0u64, |acc, (_, InteractiveTxOutput { tx_out, .. })| {
-                               acc.saturating_add(tx_out.value.to_sat())
-                       })
+               self.outputs.iter().fold(0u64, |acc, (_, output)| acc.saturating_add(output.remote_value()))
        }
 
        fn remote_inputs_weight(&self) -> Weight {
@@ -263,8 +285,8 @@ impl NegotiationContext {
                        self.inputs
                                .iter()
                                .filter(|(serial_id, _)| self.is_serial_id_valid_for_counterparty(serial_id))
-                               .fold(0u64, |weight, (_, InteractiveTxInput { prev_output, .. })| {
-                                       weight.saturating_add(estimate_input_weight(prev_output).to_wu())
+                               .fold(0u64, |weight, (_, input)| {
+                                       weight.saturating_add(estimate_input_weight(input.prev_output()).to_wu())
                                }),
                )
        }
@@ -274,8 +296,8 @@ impl NegotiationContext {
                        self.outputs
                                .iter()
                                .filter(|(serial_id, _)| self.is_serial_id_valid_for_counterparty(serial_id))
-                               .fold(0u64, |weight, (_, InteractiveTxOutput { tx_out, .. })| {
-                                       weight.saturating_add(get_output_weight(&tx_out.script_pubkey).to_wu())
+                               .fold(0u64, |weight, (_, output)| {
+                                       weight.saturating_add(get_output_weight(&output.script_pubkey()).to_wu())
                                }),
                )
        }
@@ -347,7 +369,7 @@ impl NegotiationContext {
                        },
                        hash_map::Entry::Vacant(entry) => {
                                let prev_outpoint = OutPoint { txid, vout: msg.prevtx_out };
-                               entry.insert(InteractiveTxInput {
+                               entry.insert(InteractiveTxInput::Remote(LocalOrRemoteInput {
                                        serial_id: msg.serial_id,
                                        input: TxIn {
                                                previous_output: prev_outpoint,
@@ -355,7 +377,7 @@ impl NegotiationContext {
                                                ..Default::default()
                                        },
                                        prev_output: prev_out,
-                               });
+                               }));
                                self.prevtx_outpoints.insert(prev_outpoint);
                                Ok(())
                        },
@@ -404,7 +426,7 @@ impl NegotiationContext {
                // bitcoin supply.
                let mut outputs_value: u64 = 0;
                for output in self.outputs.iter() {
-                       outputs_value = outputs_value.saturating_add(output.1.tx_out.value.to_sat());
+                       outputs_value = outputs_value.saturating_add(output.1.value());
                }
                if outputs_value.saturating_add(msg.sats) > TOTAL_BITCOIN_SUPPLY_SATOSHIS {
                        // The receiving node:
@@ -433,6 +455,23 @@ impl NegotiationContext {
                        return Err(AbortReason::InvalidOutputScript);
                }
 
+               let txout = TxOut { value: Amount::from_sat(msg.sats), script_pubkey: msg.script.clone() };
+               let is_shared = msg.script == self.expected_shared_funding_output.0;
+               let output = if is_shared {
+                       // this is a shared funding output
+                       let shared_output = self.set_actual_new_funding_output(txout)?;
+                       InteractiveTxOutput {
+                               serial_id: msg.serial_id,
+                               added_by: AddingRole::Remote,
+                               output: OutputOwned::Shared(shared_output),
+                       }
+               } else {
+                       InteractiveTxOutput {
+                               serial_id: msg.serial_id,
+                               added_by: AddingRole::Remote,
+                               output: OutputOwned::Single(txout),
+                       }
+               };
                match self.outputs.entry(msg.serial_id) {
                        hash_map::Entry::Occupied(_) => {
                                // The receiving node:
@@ -441,13 +480,7 @@ impl NegotiationContext {
                                Err(AbortReason::DuplicateSerialId)
                        },
                        hash_map::Entry::Vacant(entry) => {
-                               entry.insert(InteractiveTxOutput {
-                                       serial_id: msg.serial_id,
-                                       tx_out: TxOut {
-                                               value: Amount::from_sat(msg.sats),
-                                               script_pubkey: msg.script.clone(),
-                                       },
-                               });
+                               entry.insert(output);
                                Ok(())
                        },
                }
@@ -470,35 +503,45 @@ impl NegotiationContext {
 
        fn sent_tx_add_input(&mut self, msg: &msgs::TxAddInput) -> Result<(), AbortReason> {
                let tx = msg.prevtx.as_transaction();
-               let input = TxIn {
+               let txin = TxIn {
                        previous_output: OutPoint { txid: tx.txid(), vout: msg.prevtx_out },
                        sequence: Sequence(msg.sequence),
                        ..Default::default()
                };
-               let prev_output =
-                       tx.output.get(msg.prevtx_out as usize).ok_or(AbortReason::PrevTxOutInvalid)?.clone();
-               if !self.prevtx_outpoints.insert(input.previous_output) {
+               if !self.prevtx_outpoints.insert(txin.previous_output.clone()) {
                        // We have added an input that already exists
                        return Err(AbortReason::PrevTxOutInvalid);
                }
-               self.inputs.insert(
-                       msg.serial_id,
-                       InteractiveTxInput { serial_id: msg.serial_id, input, prev_output },
-               );
+               let vout = txin.previous_output.vout as usize;
+               let prev_output = tx.output.get(vout).ok_or(AbortReason::PrevTxOutInvalid)?.clone();
+               let input = InteractiveTxInput::Local(LocalOrRemoteInput {
+                       serial_id: msg.serial_id,
+                       input: txin,
+                       prev_output,
+               });
+               self.inputs.insert(msg.serial_id, input);
                Ok(())
        }
 
        fn sent_tx_add_output(&mut self, msg: &msgs::TxAddOutput) -> Result<(), AbortReason> {
-               self.outputs.insert(
-                       msg.serial_id,
+               let txout = TxOut { value: Amount::from_sat(msg.sats), script_pubkey: msg.script.clone() };
+               let is_shared = msg.script == self.expected_shared_funding_output.0;
+               let output = if is_shared {
+                       // this is a shared funding output
+                       let shared_output = self.set_actual_new_funding_output(txout)?;
                        InteractiveTxOutput {
                                serial_id: msg.serial_id,
-                               tx_out: TxOut {
-                                       value: Amount::from_sat(msg.sats),
-                                       script_pubkey: msg.script.clone(),
-                               },
-                       },
-               );
+                               added_by: AddingRole::Local,
+                               output: OutputOwned::Shared(shared_output),
+                       }
+               } else {
+                       InteractiveTxOutput {
+                               serial_id: msg.serial_id,
+                               added_by: AddingRole::Local,
+                               output: OutputOwned::Single(txout),
+                       }
+               };
+               self.outputs.insert(msg.serial_id, output);
                Ok(())
        }
 
@@ -554,6 +597,10 @@ impl NegotiationContext {
                        return Err(AbortReason::ExceededNumberOfInputsOrOutputs);
                }
 
+               if self.actual_new_funding_output.is_none() {
+                       return Err(AbortReason::MissingFundingOutput);
+               }
+
                // - the peer's paid feerate does not meet or exceed the agreed feerate (based on the minimum fee).
                self.check_counterparty_fees(remote_inputs_value.saturating_sub(remote_outputs_value))?;
 
@@ -762,17 +809,16 @@ macro_rules! define_state_machine_transitions {
 }
 
 impl StateMachine {
-       fn new(feerate_sat_per_kw: u32, is_initiator: bool, tx_locktime: AbsoluteLockTime) -> Self {
-               let context = NegotiationContext {
+       fn new(
+               feerate_sat_per_kw: u32, is_initiator: bool, tx_locktime: AbsoluteLockTime,
+               expected_shared_funding_output: (ScriptBuf, u64),
+       ) -> Self {
+               let context = NegotiationContext::new(
+                       is_initiator,
+                       expected_shared_funding_output,
                        tx_locktime,
-                       holder_is_initiator: is_initiator,
-                       received_tx_add_input_count: 0,
-                       received_tx_add_output_count: 0,
-                       inputs: new_hash_map(),
-                       prevtx_outpoints: new_hash_set(),
-                       outputs: new_hash_map(),
                        feerate_sat_per_kw,
-               };
+               );
                if is_initiator {
                        Self::ReceivedChangeMsg(ReceivedChangeMsg(context))
                } else {
@@ -831,11 +877,182 @@ impl StateMachine {
        ]);
 }
 
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum AddingRole {
+       Local,
+       Remote,
+}
+
+/// Represents an input -- local or remote (both have the same fields)
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct LocalOrRemoteInput {
+       serial_id: SerialId,
+       input: TxIn,
+       prev_output: TxOut,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum InteractiveTxInput {
+       Local(LocalOrRemoteInput),
+       Remote(LocalOrRemoteInput),
+       // TODO(splicing) SharedInput should be added
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct SharedOwnedOutput {
+       tx_out: TxOut,
+       local_owned: u64,
+}
+
+impl SharedOwnedOutput {
+       fn new(tx_out: TxOut, local_owned: u64) -> SharedOwnedOutput {
+               debug_assert!(
+                       local_owned <= tx_out.value.to_sat(),
+                       "SharedOwnedOutput: Inconsistent local_owned value {}, larger than output value {}",
+                       local_owned,
+                       tx_out.value
+               );
+               SharedOwnedOutput { tx_out, local_owned }
+       }
+
+       fn remote_owned(&self) -> u64 {
+               self.tx_out.value.to_sat().saturating_sub(self.local_owned)
+       }
+}
+
+/// Represents an output, with information about
+/// its control -- exclusive by the adder or shared --, and
+/// its ownership -- value fully owned by the adder or jointly
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum OutputOwned {
+       /// Belongs to local node -- controlled exclusively and fully belonging to local node
+       Single(TxOut),
+       /// Output with shared control, but fully belonging to local node
+       SharedControlFullyOwned(TxOut),
+       /// Output with shared control and joint ownership
+       Shared(SharedOwnedOutput),
+}
+
+impl OutputOwned {
+       fn tx_out(&self) -> &TxOut {
+               match self {
+                       OutputOwned::Single(tx_out) | OutputOwned::SharedControlFullyOwned(tx_out) => tx_out,
+                       OutputOwned::Shared(output) => &output.tx_out,
+               }
+       }
+
+       fn value(&self) -> u64 {
+               self.tx_out().value.to_sat()
+       }
+
+       fn is_shared(&self) -> bool {
+               match self {
+                       OutputOwned::Single(_) => false,
+                       OutputOwned::SharedControlFullyOwned(_) => true,
+                       OutputOwned::Shared(_) => true,
+               }
+       }
+
+       fn local_value(&self, local_role: AddingRole) -> u64 {
+               match self {
+                       OutputOwned::Single(tx_out) | OutputOwned::SharedControlFullyOwned(tx_out) => {
+                               match local_role {
+                                       AddingRole::Local => tx_out.value.to_sat(),
+                                       AddingRole::Remote => 0,
+                               }
+                       },
+                       OutputOwned::Shared(output) => output.local_owned,
+               }
+       }
+
+       fn remote_value(&self, local_role: AddingRole) -> u64 {
+               match self {
+                       OutputOwned::Single(tx_out) | OutputOwned::SharedControlFullyOwned(tx_out) => {
+                               match local_role {
+                                       AddingRole::Local => 0,
+                                       AddingRole::Remote => tx_out.value.to_sat(),
+                               }
+                       },
+                       OutputOwned::Shared(output) => output.remote_owned(),
+               }
+       }
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct InteractiveTxOutput {
+       serial_id: SerialId,
+       added_by: AddingRole,
+       output: OutputOwned,
+}
+
+impl InteractiveTxOutput {
+       fn tx_out(&self) -> &TxOut {
+               self.output.tx_out()
+       }
+
+       fn value(&self) -> u64 {
+               self.tx_out().value.to_sat()
+       }
+
+       fn local_value(&self) -> u64 {
+               self.output.local_value(self.added_by)
+       }
+
+       fn remote_value(&self) -> u64 {
+               self.output.remote_value(self.added_by)
+       }
+
+       fn script_pubkey(&self) -> &ScriptBuf {
+               &self.output.tx_out().script_pubkey
+       }
+}
+
+impl InteractiveTxInput {
+       pub fn serial_id(&self) -> SerialId {
+               match self {
+                       InteractiveTxInput::Local(input) => input.serial_id,
+                       InteractiveTxInput::Remote(input) => input.serial_id,
+               }
+       }
+
+       pub fn txin(&self) -> &TxIn {
+               match self {
+                       InteractiveTxInput::Local(input) => &input.input,
+                       InteractiveTxInput::Remote(input) => &input.input,
+               }
+       }
+
+       pub fn prev_output(&self) -> &TxOut {
+               match self {
+                       InteractiveTxInput::Local(input) => &input.prev_output,
+                       InteractiveTxInput::Remote(input) => &input.prev_output,
+               }
+       }
+
+       pub fn value(&self) -> u64 {
+               self.prev_output().value.to_sat()
+       }
+
+       pub fn local_value(&self) -> u64 {
+               match self {
+                       InteractiveTxInput::Local(input) => input.prev_output.value.to_sat(),
+                       InteractiveTxInput::Remote(_input) => 0,
+               }
+       }
+
+       pub fn remote_value(&self) -> u64 {
+               match self {
+                       InteractiveTxInput::Local(_input) => 0,
+                       InteractiveTxInput::Remote(input) => input.prev_output.value.to_sat(),
+               }
+       }
+}
+
 pub(crate) struct InteractiveTxConstructor {
        state_machine: StateMachine,
        channel_id: ChannelId,
        inputs_to_contribute: Vec<(SerialId, TxIn, TransactionU16LenLimited)>,
-       outputs_to_contribute: Vec<(SerialId, TxOut)>,
+       outputs_to_contribute: Vec<(SerialId, OutputOwned)>,
 }
 
 pub(crate) enum InteractiveTxMessageSend {
@@ -879,57 +1096,104 @@ pub(crate) enum HandleTxCompleteValue {
 impl InteractiveTxConstructor {
        /// Instantiates a new `InteractiveTxConstructor`.
        ///
+       /// `expected_remote_shared_funding_output`: In the case when the local node doesn't
+       /// add a shared output, but it expects a shared output to be added by the remote node,
+       /// it has to specify the script pubkey, used to determine the shared output,
+       /// and its (local) contribution from the shared output:
+       ///   0 when the whole value belongs to the remote node, or
+       ///   positive if owned also by local.
+       /// Note: The local value cannot be larger that the actual shared output.
+       ///
        /// A tuple is returned containing the newly instantiate `InteractiveTxConstructor` and optionally
        /// an initial wrapped `Tx_` message which the holder needs to send to the counterparty.
        pub fn new<ES: Deref>(
                entropy_source: &ES, channel_id: ChannelId, feerate_sat_per_kw: u32, is_initiator: bool,
                funding_tx_locktime: AbsoluteLockTime,
                inputs_to_contribute: Vec<(TxIn, TransactionU16LenLimited)>,
-               outputs_to_contribute: Vec<TxOut>,
-       ) -> (Self, Option<InteractiveTxMessageSend>)
+               outputs_to_contribute: Vec<OutputOwned>,
+               expected_remote_shared_funding_output: Option<(ScriptBuf, u64)>,
+       ) -> Result<(Self, Option<InteractiveTxMessageSend>), AbortReason>
        where
                ES::Target: EntropySource,
        {
-               let state_machine =
-                       StateMachine::new(feerate_sat_per_kw, is_initiator, funding_tx_locktime);
-               let mut inputs_to_contribute: Vec<(SerialId, TxIn, TransactionU16LenLimited)> =
-                       inputs_to_contribute
+               // Sanity check: There can be at most one shared output, local-added or remote-added
+               let mut expected_shared_funding_output: Option<(ScriptBuf, u64)> = None;
+               for output in &outputs_to_contribute {
+                       let new_output = match output {
+                               OutputOwned::Single(_tx_out) => None,
+                               OutputOwned::SharedControlFullyOwned(tx_out) => {
+                                       Some((tx_out.script_pubkey.clone(), tx_out.value.to_sat()))
+                               },
+                               OutputOwned::Shared(output) => {
+                                       // Sanity check
+                                       if output.local_owned > output.tx_out.value.to_sat() {
+                                               return Err(AbortReason::InvalidLowFundingOutputValue);
+                                       }
+                                       Some((output.tx_out.script_pubkey.clone(), output.local_owned))
+                               },
+                       };
+                       if new_output.is_some() {
+                               if expected_shared_funding_output.is_some()
+                                       || expected_remote_shared_funding_output.is_some()
+                               {
+                                       // more than one local-added shared output or
+                                       // one local-added and one remote-expected shared output
+                                       return Err(AbortReason::DuplicateFundingOutput);
+                               }
+                               expected_shared_funding_output = new_output;
+                       }
+               }
+               if let Some(expected_remote_shared_funding_output) = expected_remote_shared_funding_output {
+                       expected_shared_funding_output = Some(expected_remote_shared_funding_output);
+               }
+               if let Some(expected_shared_funding_output) = expected_shared_funding_output {
+                       let state_machine = StateMachine::new(
+                               feerate_sat_per_kw,
+                               is_initiator,
+                               funding_tx_locktime,
+                               expected_shared_funding_output,
+                       );
+                       let mut inputs_to_contribute: Vec<(SerialId, TxIn, TransactionU16LenLimited)> =
+                               inputs_to_contribute
+                                       .into_iter()
+                                       .map(|(input, tx)| {
+                                               let serial_id = generate_holder_serial_id(entropy_source, is_initiator);
+                                               (serial_id, input, tx)
+                                       })
+                                       .collect();
+                       // We'll sort by the randomly generated serial IDs, effectively shuffling the order of the inputs
+                       // as the user passed them to us to avoid leaking any potential categorization of transactions
+                       // before we pass any of the inputs to the counterparty.
+                       inputs_to_contribute.sort_unstable_by_key(|(serial_id, _, _)| *serial_id);
+                       let mut outputs_to_contribute: Vec<_> = outputs_to_contribute
                                .into_iter()
-                               .map(|(input, tx)| {
+                               .map(|output| {
                                        let serial_id = generate_holder_serial_id(entropy_source, is_initiator);
-                                       (serial_id, input, tx)
+                                       (serial_id, output)
                                })
                                .collect();
-               // We'll sort by the randomly generated serial IDs, effectively shuffling the order of the inputs
-               // as the user passed them to us to avoid leaking any potential categorization of transactions
-               // before we pass any of the inputs to the counterparty.
-               inputs_to_contribute.sort_unstable_by_key(|(serial_id, _, _)| *serial_id);
-               let mut outputs_to_contribute: Vec<(SerialId, TxOut)> = outputs_to_contribute
-                       .into_iter()
-                       .map(|output| {
-                               let serial_id = generate_holder_serial_id(entropy_source, is_initiator);
-                               (serial_id, output)
-                       })
-                       .collect();
-               // In the same manner and for the same rationale as the inputs above, we'll shuffle the outputs.
-               outputs_to_contribute.sort_unstable_by_key(|(serial_id, _)| *serial_id);
-               let mut constructor =
-                       Self { state_machine, channel_id, inputs_to_contribute, outputs_to_contribute };
-               let message_send = if is_initiator {
-                       match constructor.maybe_send_message() {
-                               Ok(msg_send) => Some(msg_send),
-                               Err(_) => {
-                                       debug_assert!(
-                                               false,
-                                               "We should always be able to start our state machine successfully"
-                                       );
-                                       None
-                               },
-                       }
+                       // In the same manner and for the same rationale as the inputs above, we'll shuffle the outputs.
+                       outputs_to_contribute.sort_unstable_by_key(|(serial_id, _)| *serial_id);
+                       let mut constructor =
+                               Self { state_machine, channel_id, inputs_to_contribute, outputs_to_contribute };
+                       let message_send = if is_initiator {
+                               match constructor.maybe_send_message() {
+                                       Ok(msg_send) => Some(msg_send),
+                                       Err(_) => {
+                                               debug_assert!(
+                                                       false,
+                                                       "We should always be able to start our state machine successfully"
+                                               );
+                                               None
+                                       },
+                               }
+                       } else {
+                               None
+                       };
+                       Ok((constructor, message_send))
                } else {
-                       None
-               };
-               (constructor, message_send)
+                       Err(AbortReason::MissingFundingOutput)
+               }
        }
 
        fn maybe_send_message(&mut self) -> Result<InteractiveTxMessageSend, AbortReason> {
@@ -942,6 +1206,7 @@ impl InteractiveTxConstructor {
                                prevtx,
                                prevtx_out: input.previous_output.vout,
                                sequence: input.sequence.to_consensus_u32(),
+                               shared_input_txid: None,
                        };
                        do_state_transition!(self, sent_tx_add_input, &msg)?;
                        Ok(InteractiveTxMessageSend::TxAddInput(msg))
@@ -949,8 +1214,8 @@ impl InteractiveTxConstructor {
                        let msg = msgs::TxAddOutput {
                                channel_id: self.channel_id,
                                serial_id,
-                               sats: output.value.to_sat(),
-                               script: output.script_pubkey,
+                               sats: output.tx_out().value.to_sat(),
+                               script: output.tx_out().script_pubkey.clone(),
                        };
                        do_state_transition!(self, sent_tx_add_output, &msg)?;
                        Ok(InteractiveTxMessageSend::TxAddOutput(msg))
@@ -1036,6 +1301,7 @@ mod tests {
        use crate::sign::EntropySource;
        use crate::util::atomic_counter::AtomicCounter;
        use crate::util::ser::TransactionU16LenLimited;
+       use bitcoin::absolute::LockTime as AbsoluteLockTime;
        use bitcoin::amount::Amount;
        use bitcoin::blockdata::opcodes;
        use bitcoin::blockdata::script::Builder;
@@ -1044,13 +1310,13 @@ mod tests {
        use bitcoin::secp256k1::{Keypair, Secp256k1};
        use bitcoin::transaction::Version;
        use bitcoin::{
-               absolute::LockTime as AbsoluteLockTime, OutPoint, Sequence, Transaction, TxIn, TxOut,
+               OutPoint, PubkeyHash, ScriptBuf, Sequence, Transaction, TxIn, TxOut, WPubkeyHash,
        };
-       use bitcoin::{PubkeyHash, ScriptBuf, WPubkeyHash, WScriptHash};
        use core::ops::Deref;
 
        use super::{
-               get_output_weight, P2TR_INPUT_WEIGHT_LOWER_BOUND, P2WPKH_INPUT_WEIGHT_LOWER_BOUND,
+               get_output_weight, AddingRole, OutputOwned, SharedOwnedOutput,
+               P2TR_INPUT_WEIGHT_LOWER_BOUND, P2WPKH_INPUT_WEIGHT_LOWER_BOUND,
                P2WSH_INPUT_WEIGHT_LOWER_BOUND, TX_COMMON_FIELDS_WEIGHT,
        };
 
@@ -1099,10 +1365,14 @@ mod tests {
        struct TestSession {
                description: &'static str,
                inputs_a: Vec<(TxIn, TransactionU16LenLimited)>,
-               outputs_a: Vec<TxOut>,
+               outputs_a: Vec<OutputOwned>,
                inputs_b: Vec<(TxIn, TransactionU16LenLimited)>,
-               outputs_b: Vec<TxOut>,
+               outputs_b: Vec<OutputOwned>,
                expect_error: Option<(AbortReason, ErrorCulprit)>,
+               /// A node adds no shared output, but expects the peer to add one, with the specific script pubkey, and local contribution
+               a_expected_remote_shared_output: Option<(ScriptBuf, u64)>,
+               /// B node adds no shared output, but expects the peer to add one, with the specific script pubkey, and local contribution
+               b_expected_remote_shared_output: Option<(ScriptBuf, u64)>,
        }
 
        fn do_test_interactive_tx_constructor(session: TestSession) {
@@ -1126,24 +1396,103 @@ mod tests {
                let channel_id = ChannelId(entropy_source.get_secure_random_bytes());
                let tx_locktime = AbsoluteLockTime::from_height(1337).unwrap();
 
-               let (mut constructor_a, first_message_a) = InteractiveTxConstructor::new(
+               // funding output sanity check
+               let shared_outputs_by_a: Vec<_> =
+                       session.outputs_a.iter().filter(|o| o.is_shared()).collect();
+               if shared_outputs_by_a.len() > 1 {
+                       println!("Test warning: Expected at most one shared output. NodeA");
+               }
+               let shared_output_by_a = if shared_outputs_by_a.len() >= 1 {
+                       Some(shared_outputs_by_a[0].value())
+               } else {
+                       None
+               };
+               let shared_outputs_by_b: Vec<_> =
+                       session.outputs_b.iter().filter(|o| o.is_shared()).collect();
+               if shared_outputs_by_b.len() > 1 {
+                       println!("Test warning: Expected at most one shared output. NodeB");
+               }
+               let shared_output_by_b = if shared_outputs_by_b.len() >= 1 {
+                       Some(shared_outputs_by_b[0].value())
+               } else {
+                       None
+               };
+               if session.a_expected_remote_shared_output.is_some()
+                       || session.b_expected_remote_shared_output.is_some()
+               {
+                       let expected_by_a = if let Some(a_expected_remote_shared_output) =
+                               &session.a_expected_remote_shared_output
+                       {
+                               a_expected_remote_shared_output.1
+                       } else {
+                               if shared_outputs_by_a.len() >= 1 {
+                                       shared_outputs_by_a[0].local_value(AddingRole::Local)
+                               } else {
+                                       0
+                               }
+                       };
+                       let expected_by_b = if let Some(b_expected_remote_shared_output) =
+                               &session.b_expected_remote_shared_output
+                       {
+                               b_expected_remote_shared_output.1
+                       } else {
+                               if shared_outputs_by_b.len() >= 1 {
+                                       shared_outputs_by_b[0].local_value(AddingRole::Local)
+                               } else {
+                                       0
+                               }
+                       };
+
+                       let expected_sum = expected_by_a + expected_by_b;
+                       let actual_shared_output =
+                               shared_output_by_a.unwrap_or(shared_output_by_b.unwrap_or(0));
+                       if expected_sum != actual_shared_output {
+                               println!("Test warning: Sum of expected shared output values does not match actual shared output value, {} {}   {} {}   {} {}", expected_sum, actual_shared_output, expected_by_a, expected_by_b, shared_output_by_a.unwrap_or(0), shared_output_by_b.unwrap_or(0));
+                       }
+               }
+
+               let (mut constructor_a, first_message_a) = match InteractiveTxConstructor::new(
                        entropy_source,
                        channel_id,
                        TEST_FEERATE_SATS_PER_KW,
                        true,
                        tx_locktime,
                        session.inputs_a,
-                       session.outputs_a,
-               );
-               let (mut constructor_b, first_message_b) = InteractiveTxConstructor::new(
+                       session.outputs_a.iter().map(|o| o.clone()).collect(),
+                       session.a_expected_remote_shared_output,
+               ) {
+                       Ok(r) => r,
+                       Err(abort_reason) => {
+                               assert_eq!(
+                                       Some((abort_reason, ErrorCulprit::NodeA)),
+                                       session.expect_error,
+                                       "Test: {}",
+                                       session.description
+                               );
+                               return;
+                       },
+               };
+               let (mut constructor_b, first_message_b) = match InteractiveTxConstructor::new(
                        entropy_source,
                        channel_id,
                        TEST_FEERATE_SATS_PER_KW,
                        false,
                        tx_locktime,
                        session.inputs_b,
-                       session.outputs_b,
-               );
+                       session.outputs_b.iter().map(|o| o.clone()).collect(),
+                       session.b_expected_remote_shared_output,
+               ) {
+                       Ok(r) => r,
+                       Err(abort_reason) => {
+                               assert_eq!(
+                                       Some((abort_reason, ErrorCulprit::NodeB)),
+                                       session.expect_error,
+                                       "Test: {}",
+                                       session.description
+                               );
+                               return;
+                       },
+               };
 
                let handle_message_send =
                        |msg: InteractiveTxMessageSend, for_constructor: &mut InteractiveTxConstructor| {
@@ -1193,7 +1542,7 @@ mod tests {
                                                        "Test: {}",
                                                        session.description
                                                );
-                                               assert!(message_send_b.is_none());
+                                               assert!(message_send_b.is_none(), "Test: {}", session.description);
                                                return;
                                        },
                                }
@@ -1217,7 +1566,7 @@ mod tests {
                                                        "Test: {}",
                                                        session.description
                                                );
-                                               assert!(message_send_a.is_none());
+                                               assert!(message_send_a.is_none(), "Test: {}", session.description);
                                                return;
                                        },
                                }
@@ -1226,12 +1575,18 @@ mod tests {
                assert!(message_send_a.is_none());
                assert!(message_send_b.is_none());
                assert_eq!(final_tx_a.unwrap().into_unsigned_tx(), final_tx_b.unwrap().into_unsigned_tx());
-               assert!(session.expect_error.is_none(), "Test: {}", session.description);
+               assert!(
+                       session.expect_error.is_none(),
+                       "Missing expected error {:?}, Test: {}",
+                       session.expect_error,
+                       session.description,
+               );
        }
 
        #[derive(Debug, Clone, Copy)]
        enum TestOutput {
                P2WPKH(u64),
+               /// P2WSH, but with the specific script used for the funding output
                P2WSH(u64),
                P2TR(u64),
                // Non-witness type to test rejection.
@@ -1245,12 +1600,8 @@ mod tests {
        fn generate_txout(output: &TestOutput) -> TxOut {
                let secp_ctx = Secp256k1::new();
                let (value, script_pubkey) = match output {
-                       TestOutput::P2WPKH(value) => {
-                               (*value, ScriptBuf::new_p2wpkh(&WPubkeyHash::from_slice(&[1; 20]).unwrap()))
-                       },
-                       TestOutput::P2WSH(value) => {
-                               (*value, ScriptBuf::new_p2wsh(&WScriptHash::from_slice(&[2; 32]).unwrap()))
-                       },
+                       TestOutput::P2WPKH(value) => (*value, generate_p2wpkh_script_pubkey()),
+                       TestOutput::P2WSH(value) => (*value, generate_funding_script_pubkey()),
                        TestOutput::P2TR(value) => (
                                *value,
                                ScriptBuf::new_p2tr(
@@ -1305,8 +1656,39 @@ mod tests {
                ScriptBuf::new_p2wpkh(&WPubkeyHash::from_slice(&[1; 20]).unwrap())
        }
 
-       fn generate_outputs(outputs: &[TestOutput]) -> Vec<TxOut> {
-               outputs.iter().map(generate_txout).collect()
+       fn generate_funding_script_pubkey() -> ScriptBuf {
+               Builder::new().push_int(33).into_script().to_p2wsh()
+       }
+
+       fn generate_output_nonfunding_one(output: &TestOutput) -> OutputOwned {
+               OutputOwned::Single(generate_txout(output))
+       }
+
+       fn generate_outputs(outputs: &[TestOutput]) -> Vec<OutputOwned> {
+               outputs.iter().map(|o| generate_output_nonfunding_one(o)).collect()
+       }
+
+       /// Generate a single output that is the funding output
+       fn generate_output(output: &TestOutput) -> Vec<OutputOwned> {
+               vec![OutputOwned::SharedControlFullyOwned(generate_txout(output))]
+       }
+
+       /// Generate a single P2WSH output that is the funding output
+       fn generate_funding_output(value: u64) -> Vec<OutputOwned> {
+               generate_output(&TestOutput::P2WSH(value))
+       }
+
+       /// Generate a single P2WSH output with shared contribution that is the funding output
+       fn generate_shared_funding_output_one(value: u64, local_value: u64) -> OutputOwned {
+               OutputOwned::Shared(SharedOwnedOutput {
+                       tx_out: generate_txout(&TestOutput::P2WSH(value)),
+                       local_owned: local_value,
+               })
+       }
+
+       /// Generate a single P2WSH output with shared contribution that is the funding output
+       fn generate_shared_funding_output(value: u64, local_value: u64) -> Vec<OutputOwned> {
+               vec![generate_shared_funding_output_one(value, local_value)]
        }
 
        fn generate_fixed_number_of_inputs(count: u16) -> Vec<(TxIn, TransactionU16LenLimited)> {
@@ -1348,7 +1730,7 @@ mod tests {
                inputs
        }
 
-       fn generate_fixed_number_of_outputs(count: u16) -> Vec<TxOut> {
+       fn generate_fixed_number_of_outputs(count: u16) -> Vec<OutputOwned> {
                // Set a constant value for each TxOut
                generate_outputs(&vec![TestOutput::P2WPKH(1_000_000); count as usize])
        }
@@ -1357,8 +1739,11 @@ mod tests {
                Builder::new().push_opcode(opcodes::OP_TRUE).into_script().to_p2sh()
        }
 
-       fn generate_non_witness_output(value: u64) -> TxOut {
-               TxOut { value: Amount::from_sat(value), script_pubkey: generate_p2sh_script_pubkey() }
+       fn generate_non_witness_output(value: u64) -> OutputOwned {
+               OutputOwned::Single(TxOut {
+                       value: Amount::from_sat(value),
+                       script_pubkey: generate_p2sh_script_pubkey(),
+               })
        }
 
        #[test]
@@ -1369,15 +1754,19 @@ mod tests {
                        outputs_a: vec![],
                        inputs_b: vec![],
                        outputs_b: vec![],
-                       expect_error: Some((AbortReason::InsufficientFees, ErrorCulprit::NodeA)),
+                       expect_error: Some((AbortReason::MissingFundingOutput, ErrorCulprit::NodeA)),
+                       a_expected_remote_shared_output: None,
+                       b_expected_remote_shared_output: None,
                });
                do_test_interactive_tx_constructor(TestSession {
                        description: "Single contribution, no initiator inputs",
                        inputs_a: vec![],
-                       outputs_a: generate_outputs(&[TestOutput::P2WPKH(1_000_000)]),
+                       outputs_a: generate_output(&TestOutput::P2WPKH(1_000_000)),
                        inputs_b: vec![],
                        outputs_b: vec![],
                        expect_error: Some((AbortReason::OutputsValueExceedsInputsValue, ErrorCulprit::NodeA)),
+                       a_expected_remote_shared_output: None,
+                       b_expected_remote_shared_output: Some((generate_p2wpkh_script_pubkey(), 0)),
                });
                do_test_interactive_tx_constructor(TestSession {
                        description: "Single contribution, no initiator outputs",
@@ -1385,15 +1774,19 @@ mod tests {
                        outputs_a: vec![],
                        inputs_b: vec![],
                        outputs_b: vec![],
-                       expect_error: None,
+                       expect_error: Some((AbortReason::MissingFundingOutput, ErrorCulprit::NodeA)),
+                       a_expected_remote_shared_output: None,
+                       b_expected_remote_shared_output: None,
                });
                do_test_interactive_tx_constructor(TestSession {
                        description: "Single contribution, no fees",
                        inputs_a: generate_inputs(&[TestOutput::P2WPKH(1_000_000)]),
-                       outputs_a: generate_outputs(&[TestOutput::P2WPKH(1_000_000)]),
+                       outputs_a: generate_output(&TestOutput::P2WPKH(1_000_000)),
                        inputs_b: vec![],
                        outputs_b: vec![],
                        expect_error: Some((AbortReason::InsufficientFees, ErrorCulprit::NodeA)),
+                       a_expected_remote_shared_output: None,
+                       b_expected_remote_shared_output: Some((generate_p2wpkh_script_pubkey(), 0)),
                });
                let p2wpkh_fee = fee_for_weight(TEST_FEERATE_SATS_PER_KW, P2WPKH_INPUT_WEIGHT_LOWER_BOUND);
                let outputs_fee = fee_for_weight(
@@ -1402,92 +1795,106 @@ mod tests {
                );
                let tx_common_fields_fee =
                        fee_for_weight(TEST_FEERATE_SATS_PER_KW, TX_COMMON_FIELDS_WEIGHT);
+
+               let amount_adjusted_with_p2wpkh_fee =
+                       1_000_000 - p2wpkh_fee - outputs_fee - tx_common_fields_fee;
                do_test_interactive_tx_constructor(TestSession {
                        description: "Single contribution, with P2WPKH input, insufficient fees",
                        inputs_a: generate_inputs(&[TestOutput::P2WPKH(1_000_000)]),
-                       outputs_a: generate_outputs(&[TestOutput::P2WPKH(
-                               1_000_000 - p2wpkh_fee - outputs_fee - tx_common_fields_fee + 1, /* makes fees insuffcient for initiator */
-                       )]),
+                       outputs_a: generate_output(&TestOutput::P2WPKH(
+                               amount_adjusted_with_p2wpkh_fee + 1, /* makes fees insuffcient for initiator */
+                       )),
                        inputs_b: vec![],
                        outputs_b: vec![],
                        expect_error: Some((AbortReason::InsufficientFees, ErrorCulprit::NodeA)),
+                       a_expected_remote_shared_output: None,
+                       b_expected_remote_shared_output: Some((generate_p2wpkh_script_pubkey(), 0)),
                });
                do_test_interactive_tx_constructor(TestSession {
                        description: "Single contribution with P2WPKH input, sufficient fees",
                        inputs_a: generate_inputs(&[TestOutput::P2WPKH(1_000_000)]),
-                       outputs_a: generate_outputs(&[TestOutput::P2WPKH(
-                               1_000_000 - p2wpkh_fee - outputs_fee - tx_common_fields_fee,
-                       )]),
+                       outputs_a: generate_output(&TestOutput::P2WPKH(amount_adjusted_with_p2wpkh_fee)),
                        inputs_b: vec![],
                        outputs_b: vec![],
                        expect_error: None,
+                       a_expected_remote_shared_output: None,
+                       b_expected_remote_shared_output: Some((generate_p2wpkh_script_pubkey(), 0)),
                });
                let p2wsh_fee = fee_for_weight(TEST_FEERATE_SATS_PER_KW, P2WSH_INPUT_WEIGHT_LOWER_BOUND);
+               let amount_adjusted_with_p2wsh_fee =
+                       1_000_000 - p2wsh_fee - outputs_fee - tx_common_fields_fee;
                do_test_interactive_tx_constructor(TestSession {
                        description: "Single contribution, with P2WSH input, insufficient fees",
                        inputs_a: generate_inputs(&[TestOutput::P2WSH(1_000_000)]),
-                       outputs_a: generate_outputs(&[TestOutput::P2WPKH(
-                               1_000_000 - p2wsh_fee - outputs_fee - tx_common_fields_fee + 1, /* makes fees insuffcient for initiator */
-                       )]),
+                       outputs_a: generate_output(&TestOutput::P2WPKH(
+                               amount_adjusted_with_p2wsh_fee + 1, /* makes fees insuffcient for initiator */
+                       )),
                        inputs_b: vec![],
                        outputs_b: vec![],
                        expect_error: Some((AbortReason::InsufficientFees, ErrorCulprit::NodeA)),
+                       a_expected_remote_shared_output: None,
+                       b_expected_remote_shared_output: Some((generate_p2wpkh_script_pubkey(), 0)),
                });
                do_test_interactive_tx_constructor(TestSession {
                        description: "Single contribution with P2WSH input, sufficient fees",
                        inputs_a: generate_inputs(&[TestOutput::P2WSH(1_000_000)]),
-                       outputs_a: generate_outputs(&[TestOutput::P2WPKH(
-                               1_000_000 - p2wsh_fee - outputs_fee - tx_common_fields_fee,
-                       )]),
+                       outputs_a: generate_output(&TestOutput::P2WPKH(amount_adjusted_with_p2wsh_fee)),
                        inputs_b: vec![],
                        outputs_b: vec![],
                        expect_error: None,
+                       a_expected_remote_shared_output: None,
+                       b_expected_remote_shared_output: Some((generate_p2wpkh_script_pubkey(), 0)),
                });
                let p2tr_fee = fee_for_weight(TEST_FEERATE_SATS_PER_KW, P2TR_INPUT_WEIGHT_LOWER_BOUND);
+               let amount_adjusted_with_p2tr_fee =
+                       1_000_000 - p2tr_fee - outputs_fee - tx_common_fields_fee;
                do_test_interactive_tx_constructor(TestSession {
                        description: "Single contribution, with P2TR input, insufficient fees",
                        inputs_a: generate_inputs(&[TestOutput::P2TR(1_000_000)]),
-                       outputs_a: generate_outputs(&[TestOutput::P2WPKH(
-                               1_000_000 - p2tr_fee - outputs_fee - tx_common_fields_fee + 1, /* makes fees insuffcient for initiator */
-                       )]),
+                       outputs_a: generate_output(&TestOutput::P2WPKH(
+                               amount_adjusted_with_p2tr_fee + 1, /* makes fees insuffcient for initiator */
+                       )),
                        inputs_b: vec![],
                        outputs_b: vec![],
                        expect_error: Some((AbortReason::InsufficientFees, ErrorCulprit::NodeA)),
+                       a_expected_remote_shared_output: None,
+                       b_expected_remote_shared_output: Some((generate_p2wpkh_script_pubkey(), 0)),
                });
                do_test_interactive_tx_constructor(TestSession {
                        description: "Single contribution with P2TR input, sufficient fees",
                        inputs_a: generate_inputs(&[TestOutput::P2TR(1_000_000)]),
-                       outputs_a: generate_outputs(&[TestOutput::P2WPKH(
-                               1_000_000 - p2tr_fee - outputs_fee - tx_common_fields_fee,
-                       )]),
+                       outputs_a: generate_output(&TestOutput::P2WPKH(amount_adjusted_with_p2tr_fee)),
                        inputs_b: vec![],
                        outputs_b: vec![],
                        expect_error: None,
+                       a_expected_remote_shared_output: None,
+                       b_expected_remote_shared_output: Some((generate_p2wpkh_script_pubkey(), 0)),
                });
                do_test_interactive_tx_constructor(TestSession {
                        description: "Initiator contributes sufficient fees, but non-initiator does not",
                        inputs_a: generate_inputs(&[TestOutput::P2WPKH(1_000_000)]),
                        outputs_a: vec![],
                        inputs_b: generate_inputs(&[TestOutput::P2WPKH(100_000)]),
-                       outputs_b: generate_outputs(&[TestOutput::P2WPKH(100_000)]),
+                       outputs_b: generate_output(&TestOutput::P2WPKH(100_000)),
                        expect_error: Some((AbortReason::InsufficientFees, ErrorCulprit::NodeB)),
+                       a_expected_remote_shared_output: Some((generate_p2wpkh_script_pubkey(), 0)),
+                       b_expected_remote_shared_output: None,
                });
                do_test_interactive_tx_constructor(TestSession {
                        description: "Multi-input-output contributions from both sides",
                        inputs_a: generate_inputs(&[TestOutput::P2WPKH(1_000_000); 2]),
-                       outputs_a: generate_outputs(&[
-                               TestOutput::P2WPKH(1_000_000),
-                               TestOutput::P2WPKH(200_000),
-                       ]),
+                       outputs_a: vec![
+                               generate_shared_funding_output_one(1_000_000, 200_000),
+                               generate_output_nonfunding_one(&TestOutput::P2WPKH(200_000)),
+                       ],
                        inputs_b: generate_inputs(&[
                                TestOutput::P2WPKH(1_000_000),
                                TestOutput::P2WPKH(500_000),
                        ]),
-                       outputs_b: generate_outputs(&[
-                               TestOutput::P2WPKH(1_000_000),
-                               TestOutput::P2WPKH(400_000),
-                       ]),
+                       outputs_b: vec![generate_output_nonfunding_one(&TestOutput::P2WPKH(400_000))],
                        expect_error: None,
+                       a_expected_remote_shared_output: None,
+                       b_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 800_000)),
                });
 
                do_test_interactive_tx_constructor(TestSession {
@@ -1497,6 +1904,8 @@ mod tests {
                        inputs_b: vec![],
                        outputs_b: vec![],
                        expect_error: Some((AbortReason::PrevTxOutInvalid, ErrorCulprit::NodeA)),
+                       a_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 0)),
+                       b_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 0)),
                });
 
                let tx =
@@ -1508,10 +1917,12 @@ mod tests {
                do_test_interactive_tx_constructor(TestSession {
                        description: "Invalid input sequence from initiator",
                        inputs_a: vec![(invalid_sequence_input, tx.clone())],
-                       outputs_a: generate_outputs(&[TestOutput::P2WPKH(1_000_000)]),
+                       outputs_a: generate_output(&TestOutput::P2WPKH(1_000_000)),
                        inputs_b: vec![],
                        outputs_b: vec![],
                        expect_error: Some((AbortReason::IncorrectInputSequenceValue, ErrorCulprit::NodeA)),
+                       a_expected_remote_shared_output: None,
+                       b_expected_remote_shared_output: Some((generate_p2wpkh_script_pubkey(), 0)),
                });
                let duplicate_input = TxIn {
                        previous_output: OutPoint { txid: tx.as_transaction().txid(), vout: 0 },
@@ -1521,11 +1932,14 @@ mod tests {
                do_test_interactive_tx_constructor(TestSession {
                        description: "Duplicate prevout from initiator",
                        inputs_a: vec![(duplicate_input.clone(), tx.clone()), (duplicate_input, tx.clone())],
-                       outputs_a: generate_outputs(&[TestOutput::P2WPKH(1_000_000)]),
+                       outputs_a: generate_output(&TestOutput::P2WPKH(1_000_000)),
                        inputs_b: vec![],
                        outputs_b: vec![],
                        expect_error: Some((AbortReason::PrevTxOutInvalid, ErrorCulprit::NodeB)),
+                       a_expected_remote_shared_output: None,
+                       b_expected_remote_shared_output: Some((generate_p2wpkh_script_pubkey(), 0)),
                });
+               // Non-initiator uses same prevout as initiator.
                let duplicate_input = TxIn {
                        previous_output: OutPoint { txid: tx.as_transaction().txid(), vout: 0 },
                        sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
@@ -1534,10 +1948,27 @@ mod tests {
                do_test_interactive_tx_constructor(TestSession {
                        description: "Non-initiator uses same prevout as initiator",
                        inputs_a: vec![(duplicate_input.clone(), tx.clone())],
-                       outputs_a: generate_outputs(&[TestOutput::P2WPKH(1_000_000)]),
+                       outputs_a: generate_shared_funding_output(1_000_000, 905_000),
                        inputs_b: vec![(duplicate_input.clone(), tx.clone())],
                        outputs_b: vec![],
                        expect_error: Some((AbortReason::PrevTxOutInvalid, ErrorCulprit::NodeA)),
+                       a_expected_remote_shared_output: None,
+                       b_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 95_000)),
+               });
+               let duplicate_input = TxIn {
+                       previous_output: OutPoint { txid: tx.as_transaction().txid(), vout: 0 },
+                       sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
+                       ..Default::default()
+               };
+               do_test_interactive_tx_constructor(TestSession {
+                       description: "Non-initiator uses same prevout as initiator",
+                       inputs_a: vec![(duplicate_input.clone(), tx.clone())],
+                       outputs_a: generate_output(&TestOutput::P2WPKH(1_000_000)),
+                       inputs_b: vec![(duplicate_input.clone(), tx.clone())],
+                       outputs_b: vec![],
+                       expect_error: Some((AbortReason::PrevTxOutInvalid, ErrorCulprit::NodeA)),
+                       a_expected_remote_shared_output: None,
+                       b_expected_remote_shared_output: Some((generate_p2wpkh_script_pubkey(), 0)),
                });
                do_test_interactive_tx_constructor(TestSession {
                        description: "Initiator sends too many TxAddInputs",
@@ -1546,6 +1977,8 @@ mod tests {
                        inputs_b: vec![],
                        outputs_b: vec![],
                        expect_error: Some((AbortReason::ReceivedTooManyTxAddInputs, ErrorCulprit::NodeA)),
+                       a_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 0)),
+                       b_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 0)),
                });
                do_test_interactive_tx_constructor_with_entropy_source(
                        TestSession {
@@ -1556,6 +1989,8 @@ mod tests {
                                inputs_b: vec![],
                                outputs_b: vec![],
                                expect_error: Some((AbortReason::DuplicateSerialId, ErrorCulprit::NodeA)),
+                               a_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 0)),
+                               b_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 0)),
                        },
                        &DuplicateEntropySource,
                );
@@ -1566,24 +2001,30 @@ mod tests {
                        inputs_b: vec![],
                        outputs_b: vec![],
                        expect_error: Some((AbortReason::ReceivedTooManyTxAddOutputs, ErrorCulprit::NodeA)),
+                       a_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 0)),
+                       b_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 0)),
                });
                do_test_interactive_tx_constructor(TestSession {
                        description: "Initiator sends an output below dust value",
                        inputs_a: vec![],
-                       outputs_a: generate_outputs(&[TestOutput::P2WSH(
+                       outputs_a: generate_funding_output(
                                generate_p2wsh_script_pubkey().dust_value().to_sat() - 1,
-                       )]),
+                       ),
                        inputs_b: vec![],
                        outputs_b: vec![],
                        expect_error: Some((AbortReason::BelowDustLimit, ErrorCulprit::NodeA)),
+                       a_expected_remote_shared_output: None,
+                       b_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 0)),
                });
                do_test_interactive_tx_constructor(TestSession {
                        description: "Initiator sends an output above maximum sats allowed",
                        inputs_a: vec![],
-                       outputs_a: generate_outputs(&[TestOutput::P2WPKH(TOTAL_BITCOIN_SUPPLY_SATOSHIS + 1)]),
+                       outputs_a: generate_output(&TestOutput::P2WPKH(TOTAL_BITCOIN_SUPPLY_SATOSHIS + 1)),
                        inputs_b: vec![],
                        outputs_b: vec![],
                        expect_error: Some((AbortReason::ExceededMaximumSatsAllowed, ErrorCulprit::NodeA)),
+                       a_expected_remote_shared_output: None,
+                       b_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 0)),
                });
                do_test_interactive_tx_constructor(TestSession {
                        description: "Initiator sends an output without a witness program",
@@ -1592,6 +2033,8 @@ mod tests {
                        inputs_b: vec![],
                        outputs_b: vec![],
                        expect_error: Some((AbortReason::InvalidOutputScript, ErrorCulprit::NodeA)),
+                       a_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 0)),
+                       b_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 0)),
                });
                do_test_interactive_tx_constructor_with_entropy_source(
                        TestSession {
@@ -1602,6 +2045,8 @@ mod tests {
                                inputs_b: vec![],
                                outputs_b: vec![],
                                expect_error: Some((AbortReason::DuplicateSerialId, ErrorCulprit::NodeA)),
+                               a_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 0)),
+                               b_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 0)),
                        },
                        &DuplicateEntropySource,
                );
@@ -1609,10 +2054,12 @@ mod tests {
                do_test_interactive_tx_constructor(TestSession {
                        description: "Peer contributed more output value than inputs",
                        inputs_a: generate_inputs(&[TestOutput::P2WPKH(100_000)]),
-                       outputs_a: generate_outputs(&[TestOutput::P2WPKH(1_000_000)]),
+                       outputs_a: generate_output(&TestOutput::P2WPKH(1_000_000)),
                        inputs_b: vec![],
                        outputs_b: vec![],
                        expect_error: Some((AbortReason::OutputsValueExceedsInputsValue, ErrorCulprit::NodeA)),
+                       a_expected_remote_shared_output: None,
+                       b_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 0)),
                });
 
                do_test_interactive_tx_constructor(TestSession {
@@ -1625,6 +2072,8 @@ mod tests {
                                AbortReason::ExceededNumberOfInputsOrOutputs,
                                ErrorCulprit::Indeterminate,
                        )),
+                       a_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 0)),
+                       b_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 0)),
                });
                do_test_interactive_tx_constructor(TestSession {
                        description: "Peer contributed more than allowed number of outputs",
@@ -1636,6 +2085,121 @@ mod tests {
                                AbortReason::ExceededNumberOfInputsOrOutputs,
                                ErrorCulprit::Indeterminate,
                        )),
+                       a_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 0)),
+                       b_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 0)),
+               });
+
+               // Adding multiple outputs to the funding output pubkey is an error
+               do_test_interactive_tx_constructor(TestSession {
+                       description: "Adding two outputs to the funding output pubkey",
+                       inputs_a: generate_inputs(&[TestOutput::P2WPKH(1_000_000)]),
+                       outputs_a: generate_funding_output(100_000),
+                       inputs_b: generate_inputs(&[TestOutput::P2WPKH(1_001_000)]),
+                       outputs_b: generate_funding_output(100_000),
+                       expect_error: Some((AbortReason::DuplicateFundingOutput, ErrorCulprit::NodeA)),
+                       a_expected_remote_shared_output: None,
+                       b_expected_remote_shared_output: None,
+               });
+
+               // We add the funding output, but we contribute a little
+               do_test_interactive_tx_constructor(TestSession {
+                       description: "Funding output by us, small contribution",
+                       inputs_a: generate_inputs(&[TestOutput::P2WPKH(12_000)]),
+                       outputs_a: generate_shared_funding_output(1_000_000, 10_000),
+                       inputs_b: generate_inputs(&[TestOutput::P2WPKH(992_000)]),
+                       outputs_b: vec![],
+                       expect_error: None,
+                       a_expected_remote_shared_output: None,
+                       b_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 990_000)),
+               });
+
+               // They add the funding output, and we contribute a little
+               do_test_interactive_tx_constructor(TestSession {
+                       description: "Funding output by them, small contribution",
+                       inputs_a: generate_inputs(&[TestOutput::P2WPKH(12_000)]),
+                       outputs_a: vec![],
+                       inputs_b: generate_inputs(&[TestOutput::P2WPKH(992_000)]),
+                       outputs_b: generate_shared_funding_output(1_000_000, 990_000),
+                       expect_error: None,
+                       a_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 10_000)),
+                       b_expected_remote_shared_output: None,
+               });
+
+               // We add the funding output, and we contribute most
+               do_test_interactive_tx_constructor(TestSession {
+                       description: "Funding output by us, large contribution",
+                       inputs_a: generate_inputs(&[TestOutput::P2WPKH(992_000)]),
+                       outputs_a: generate_shared_funding_output(1_000_000, 990_000),
+                       inputs_b: generate_inputs(&[TestOutput::P2WPKH(12_000)]),
+                       outputs_b: vec![],
+                       expect_error: None,
+                       a_expected_remote_shared_output: None,
+                       b_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 10_000)),
+               });
+
+               // They add the funding output, but we contribute most
+               do_test_interactive_tx_constructor(TestSession {
+                       description: "Funding output by them, large contribution",
+                       inputs_a: generate_inputs(&[TestOutput::P2WPKH(992_000)]),
+                       outputs_a: vec![],
+                       inputs_b: generate_inputs(&[TestOutput::P2WPKH(12_000)]),
+                       outputs_b: generate_shared_funding_output(1_000_000, 10_000),
+                       expect_error: None,
+                       a_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 990_000)),
+                       b_expected_remote_shared_output: None,
+               });
+
+               // During a splice-out, with peer providing more output value than input value
+               // but still pays enough fees due to their to_remote_value_satoshis portion in
+               // the shared input.
+               do_test_interactive_tx_constructor(TestSession {
+                       description: "Splice out with sufficient initiator balance",
+                       inputs_a: generate_inputs(&[TestOutput::P2WPKH(100_000), TestOutput::P2WPKH(50_000)]),
+                       outputs_a: generate_funding_output(120_000),
+                       inputs_b: generate_inputs(&[TestOutput::P2WPKH(50_000)]),
+                       outputs_b: vec![],
+                       expect_error: None,
+                       a_expected_remote_shared_output: None,
+                       b_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 0)),
+               });
+
+               // During a splice-out, with peer providing more output value than input value
+               // and the to_remote_value_satoshis portion in
+               // the shared input cannot cover fees
+               do_test_interactive_tx_constructor(TestSession {
+                       description: "Splice out with insufficient initiator balance",
+                       inputs_a: generate_inputs(&[TestOutput::P2WPKH(100_000), TestOutput::P2WPKH(15_000)]),
+                       outputs_a: generate_funding_output(120_000),
+                       inputs_b: generate_inputs(&[TestOutput::P2WPKH(85_000)]),
+                       outputs_b: vec![],
+                       expect_error: Some((AbortReason::OutputsValueExceedsInputsValue, ErrorCulprit::NodeA)),
+                       a_expected_remote_shared_output: None,
+                       b_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 0)),
+               });
+
+               // The actual funding output value is lower than the intended local contribution by the same node
+               do_test_interactive_tx_constructor(TestSession {
+                       description: "Splice in, invalid intended local contribution",
+                       inputs_a: generate_inputs(&[TestOutput::P2WPKH(100_000), TestOutput::P2WPKH(15_000)]),
+                       outputs_a: generate_shared_funding_output(100_000, 120_000), // local value is higher than the output value
+                       inputs_b: generate_inputs(&[TestOutput::P2WPKH(85_000)]),
+                       outputs_b: vec![],
+                       expect_error: Some((AbortReason::InvalidLowFundingOutputValue, ErrorCulprit::NodeA)),
+                       a_expected_remote_shared_output: None,
+                       b_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 20_000)),
+               });
+
+               // The actual funding output value is lower than the intended local contribution of the other node
+               do_test_interactive_tx_constructor(TestSession {
+                       description: "Splice in, invalid intended local contribution",
+                       inputs_a: generate_inputs(&[TestOutput::P2WPKH(100_000), TestOutput::P2WPKH(15_000)]),
+                       outputs_a: vec![],
+                       inputs_b: generate_inputs(&[TestOutput::P2WPKH(85_000)]),
+                       outputs_b: generate_funding_output(100_000),
+                       // The error is caused by NodeA, it occurs when nodeA prepares the message to be sent to NodeB, that's why here it shows up as NodeB
+                       expect_error: Some((AbortReason::InvalidLowFundingOutputValue, ErrorCulprit::NodeB)),
+                       a_expected_remote_shared_output: Some((generate_funding_script_pubkey(), 120_000)), // this is higher than the actual output value
+                       b_expected_remote_shared_output: None,
                });
        }
 
index 0cc8f13236d5cbf5d498002dcd8dee7fd78bdff9..8f389d07cefc9b59cb314b58a597ac75d23aa199 100644 (file)
@@ -410,8 +410,9 @@ pub struct ChannelReady {
 /// construction.
 pub type SerialId = u64;
 
-/// An stfu (quiescence) message to be sent by or received from the stfu initiator.
-// TODO(splicing): Add spec link for `stfu`; still in draft, using from https://github.com/lightning/bolts/pull/863
+/// An `stfu` (quiescence) message to be sent by or received from the stfu initiator.
+///
+// TODO(splicing): Add spec link for `stfu`; still in draft, using from https://github.com/lightning/bolts/pull/1160
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct Stfu {
        /// The channel ID where quiescence is intended
@@ -420,48 +421,51 @@ pub struct Stfu {
        pub initiator: u8,
 }
 
-/// A splice message to be sent by or received from the stfu initiator (splice initiator).
-// TODO(splicing): Add spec link for `splice`; still in draft, using from https://github.com/lightning/bolts/pull/863
+/// A `splice_init` message to be sent by or received from the stfu initiator (splice initiator).
+///
+// TODO(splicing): Add spec link for `splice_init`; still in draft, using from https://github.com/lightning/bolts/pull/1160
 #[derive(Clone, Debug, PartialEq, Eq)]
-pub struct Splice {
+pub struct SpliceInit {
        /// The channel ID where splicing is intended
        pub channel_id: ChannelId,
-       /// The genesis hash of the blockchain where the channel is intended to be spliced
-       pub chain_hash: ChainHash,
-       /// The intended change in channel capacity: the amount to be added (positive value)
-       /// or removed (negative value) by the sender (splice initiator) by splicing into/from the channel.
-       pub relative_satoshis: i64,
+       /// The amount the splice initiator is intending to add to its channel balance (splice-in)
+       /// or remove from its channel balance (splice-out).
+       pub funding_contribution_satoshis: i64,
        /// The feerate for the new funding transaction, set by the splice initiator
        pub funding_feerate_perkw: u32,
        /// The locktime for the new funding transaction
        pub locktime: u32,
        /// The key of the sender (splice initiator) controlling the new funding transaction
        pub funding_pubkey: PublicKey,
+       /// If set, only confirmed inputs added (by the splice acceptor) will be accepted
+       pub require_confirmed_inputs: Option<()>,
 }
 
-/// A splice_ack message to be received by or sent to the splice initiator.
+/// A `splice_ack` message to be received by or sent to the splice initiator.
 ///
-// TODO(splicing): Add spec link for `splice_ack`; still in draft, using from https://github.com/lightning/bolts/pull/863
+// TODO(splicing): Add spec link for `splice_ack`; still in draft, using from https://github.com/lightning/bolts/pull/1160
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct SpliceAck {
        /// The channel ID where splicing is intended
        pub channel_id: ChannelId,
-       /// The genesis hash of the blockchain where the channel is intended to be spliced
-       pub chain_hash: ChainHash,
-       /// The intended change in channel capacity: the amount to be added (positive value)
-       /// or removed (negative value) by the sender (splice acceptor) by splicing into/from the channel.
-       pub relative_satoshis: i64,
+       /// The amount the splice acceptor is intending to add to its channel balance (splice-in)
+       /// or remove from its channel balance (splice-out).
+       pub funding_contribution_satoshis: i64,
        /// The key of the sender (splice acceptor) controlling the new funding transaction
        pub funding_pubkey: PublicKey,
+       /// If set, only confirmed inputs added (by the splice initiator) will be accepted
+       pub require_confirmed_inputs: Option<()>,
 }
 
-/// A splice_locked message to be sent to or received from a peer.
+/// A `splice_locked` message to be sent to or received from a peer.
 ///
-// TODO(splicing): Add spec link for `splice_locked`; still in draft, using from https://github.com/lightning/bolts/pull/863
+// TODO(splicing): Add spec link for `splice_locked`; still in draft, using from https://github.com/lightning/bolts/pull/1160
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct SpliceLocked {
        /// The channel ID
        pub channel_id: ChannelId,
+       /// The ID of the new funding transaction that has been locked
+       pub splice_txid: Txid,
 }
 
 /// A tx_add_input message for adding an input during interactive transaction construction
@@ -481,6 +485,8 @@ pub struct TxAddInput {
        pub prevtx_out: u32,
        /// The sequence number of this input
        pub sequence: u32,
+       /// The ID of the previous funding transaction, when it is being added as an input during splicing
+       pub shared_input_txid: Option<Txid>,
 }
 
 /// A tx_add_output message for adding an output during interactive transaction construction.
@@ -544,7 +550,7 @@ pub struct TxSignatures {
        /// The list of witnesses
        pub witnesses: Vec<Witness>,
        /// Optional signature for the shared input -- the previous funding outpoint -- signed by both peers
-       pub funding_outpoint_sig: Option<Signature>,
+       pub shared_input_signature: Option<Signature>,
 }
 
 /// A tx_init_rbf message which initiates a replacement of the transaction after it's been
@@ -708,6 +714,15 @@ pub struct UpdateFailMalformedHTLC {
        pub failure_code: u16,
 }
 
+/// Optional batch parameters for `commitment_signed` message.
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+pub struct CommitmentSignedBatch {
+       /// Batch size N: all N `commitment_signed` messages must be received before being processed
+       pub batch_size: u16,
+       /// The funding transaction, to discriminate among multiple pending funding transactions (e.g. in case of splicing)
+       pub funding_txid: Txid,
+}
+
 /// A [`commitment_signed`] message to be sent to or received from a peer.
 ///
 /// [`commitment_signed`]: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#committing-updates-so-far-commitment_signed
@@ -719,6 +734,8 @@ pub struct CommitmentSigned {
        pub signature: Signature,
        /// Signatures on the HTLC transactions
        pub htlc_signatures: Vec<Signature>,
+       /// Optional batch size and other parameters
+       pub batch: Option<CommitmentSignedBatch>,
        #[cfg(taproot)]
        /// The partial Taproot signature on the commitment transaction
        pub partial_signature_with_nonce: Option<PartialSignatureWithNonce>,
@@ -1464,9 +1481,9 @@ pub trait ChannelMessageHandler : MessageSendEventsProvider {
        fn handle_stfu(&self, their_node_id: &PublicKey, msg: &Stfu);
 
        // Splicing
-       /// Handle an incoming `splice` message from the given peer.
+       /// Handle an incoming `splice_init` message from the given peer.
        #[cfg(splicing)]
-       fn handle_splice(&self, their_node_id: &PublicKey, msg: &Splice);
+       fn handle_splice_init(&self, their_node_id: &PublicKey, msg: &SpliceInit);
        /// Handle an incoming `splice_ack` message from the given peer.
        #[cfg(splicing)]
        fn handle_splice_ack(&self, their_node_id: &PublicKey, msg: &SpliceAck);
@@ -2091,24 +2108,27 @@ impl_writeable_msg!(Stfu, {
        initiator,
 }, {});
 
-impl_writeable_msg!(Splice, {
+impl_writeable_msg!(SpliceInit, {
        channel_id,
-       chain_hash,
-       relative_satoshis,
+       funding_contribution_satoshis,
        funding_feerate_perkw,
        locktime,
        funding_pubkey,
-}, {});
+}, {
+       (2, require_confirmed_inputs, option), // `splice_init_tlvs`
+});
 
 impl_writeable_msg!(SpliceAck, {
        channel_id,
-       chain_hash,
-       relative_satoshis,
+       funding_contribution_satoshis,
        funding_pubkey,
-}, {});
+}, {
+       (2, require_confirmed_inputs, option), // `splice_ack_tlvs`
+});
 
 impl_writeable_msg!(SpliceLocked, {
        channel_id,
+       splice_txid,
 }, {});
 
 impl_writeable_msg!(TxAddInput, {
@@ -2117,7 +2137,9 @@ impl_writeable_msg!(TxAddInput, {
        prevtx,
        prevtx_out,
        sequence,
-}, {});
+}, {
+       (0, shared_input_txid, option), // `funding_txid`
+});
 
 impl_writeable_msg!(TxAddOutput, {
        channel_id,
@@ -2145,7 +2167,7 @@ impl_writeable_msg!(TxSignatures, {
        tx_hash,
        witnesses,
 }, {
-       (0, funding_outpoint_sig, option),
+       (0, shared_input_signature, option), // `signature`
 });
 
 impl_writeable_msg!(TxInitRbf, {
@@ -2194,12 +2216,19 @@ impl_writeable!(ClosingSignedFeeRange, {
        max_fee_satoshis
 });
 
+impl_writeable_msg!(CommitmentSignedBatch, {
+       batch_size,
+       funding_txid,
+}, {});
+
 #[cfg(not(taproot))]
 impl_writeable_msg!(CommitmentSigned, {
        channel_id,
        signature,
        htlc_signatures
-}, {});
+}, {
+       (0, batch, option),
+});
 
 #[cfg(taproot)]
 impl_writeable_msg!(CommitmentSigned, {
@@ -2207,7 +2236,8 @@ impl_writeable_msg!(CommitmentSigned, {
        signature,
        htlc_signatures
 }, {
-       (2, partial_signature_with_nonce, option)
+       (0, batch, option),
+       (2, partial_signature_with_nonce, option),
 });
 
 impl_writeable!(DecodedOnionErrorPacket, {
@@ -3859,19 +3889,19 @@ mod tests {
        }
 
        #[test]
-       fn encoding_splice() {
+       fn encoding_splice_init() {
                let secp_ctx = Secp256k1::new();
                let (_, pubkey_1,) = get_keys_from!("0101010101010101010101010101010101010101010101010101010101010101", secp_ctx);
-               let splice = msgs::Splice {
-                       chain_hash: ChainHash::from_hex("6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000").unwrap(),
+               let splice_init = msgs::SpliceInit {
                        channel_id: ChannelId::from_bytes([2; 32]),
-                       relative_satoshis: 123456,
+                       funding_contribution_satoshis: -123456,
                        funding_feerate_perkw: 2000,
                        locktime: 0,
                        funding_pubkey: pubkey_1,
+                       require_confirmed_inputs: Some(()),
                };
-               let encoded_value = splice.encode();
-               assert_eq!(encoded_value.as_hex().to_string(), "02020202020202020202020202020202020202020202020202020202020202026fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000000000000001e240000007d000000000031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f");
+               let encoded_value = splice_init.encode();
+               assert_eq!(encoded_value.as_hex().to_string(), "0202020202020202020202020202020202020202020202020202020202020202fffffffffffe1dc0000007d000000000031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f0200");
        }
 
        #[test]
@@ -3888,23 +3918,24 @@ mod tests {
        fn encoding_splice_ack() {
                let secp_ctx = Secp256k1::new();
                let (_, pubkey_1,) = get_keys_from!("0101010101010101010101010101010101010101010101010101010101010101", secp_ctx);
-               let splice = msgs::SpliceAck {
-                       chain_hash: ChainHash::from_hex("6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000").unwrap(),
+               let splice_ack = msgs::SpliceAck {
                        channel_id: ChannelId::from_bytes([2; 32]),
-                       relative_satoshis: 123456,
+                       funding_contribution_satoshis: -123456,
                        funding_pubkey: pubkey_1,
+                       require_confirmed_inputs: Some(()),
                };
-               let encoded_value = splice.encode();
-               assert_eq!(encoded_value.as_hex().to_string(), "02020202020202020202020202020202020202020202020202020202020202026fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000000000000001e240031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f");
+               let encoded_value = splice_ack.encode();
+               assert_eq!(encoded_value.as_hex().to_string(), "0202020202020202020202020202020202020202020202020202020202020202fffffffffffe1dc0031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f0200");
        }
 
        #[test]
        fn encoding_splice_locked() {
-               let splice = msgs::SpliceLocked {
+               let splice_locked = msgs::SpliceLocked {
                        channel_id: ChannelId::from_bytes([2; 32]),
+                       splice_txid: Txid::from_str("c2d4449afa8d26140898dd54d3390b057ba2a5afcf03ba29d7dc0d8b9ffe966e").unwrap(),
                };
-               let encoded_value = splice.encode();
-               assert_eq!(encoded_value.as_hex().to_string(), "0202020202020202020202020202020202020202020202020202020202020202");
+               let encoded_value = splice_locked.encode();
+               assert_eq!(encoded_value.as_hex().to_string(), "02020202020202020202020202020202020202020202020202020202020202026e96fe9f8b0ddcd729ba03cfafa5a27b050b39d354dd980814268dfa9a44d4c2");
        }
 
        #[test]
@@ -3936,10 +3967,11 @@ mod tests {
                        }).unwrap(),
                        prevtx_out: 305419896,
                        sequence: 305419896,
+                       shared_input_txid: Some(Txid::from_str("c2d4449afa8d26140898dd54d3390b057ba2a5afcf03ba29d7dc0d8b9ffe966e").unwrap()),
                };
                let encoded_value = tx_add_input.encode();
-               let target_value = <Vec<u8>>::from_hex("0202020202020202020202020202020202020202020202020202020202020202000000012345678900de02000000000101779ced6c148293f86b60cb222108553d22c89207326bb7b6b897e23e64ab5b300200000000fdffffff0236dbc1000000000016001417d29e4dd454bac3b1cde50d1926da80cfc5287b9cbd03000000000016001436ec78d514df462da95e6a00c24daa8915362d420247304402206af85b7dd67450ad12c979302fac49dfacbc6a8620f49c5da2b5721cf9565ca502207002b32fed9ce1bf095f57aeb10c36928ac60b12e723d97d2964a54640ceefa701210301ab7dc16488303549bfcdd80f6ae5ee4c20bf97ab5410bbd6b1bfa85dcd6944000000001234567812345678").unwrap();
-               assert_eq!(encoded_value, target_value);
+               let target_value = "0202020202020202020202020202020202020202020202020202020202020202000000012345678900de02000000000101779ced6c148293f86b60cb222108553d22c89207326bb7b6b897e23e64ab5b300200000000fdffffff0236dbc1000000000016001417d29e4dd454bac3b1cde50d1926da80cfc5287b9cbd03000000000016001436ec78d514df462da95e6a00c24daa8915362d420247304402206af85b7dd67450ad12c979302fac49dfacbc6a8620f49c5da2b5721cf9565ca502207002b32fed9ce1bf095f57aeb10c36928ac60b12e723d97d2964a54640ceefa701210301ab7dc16488303549bfcdd80f6ae5ee4c20bf97ab5410bbd6b1bfa85dcd694400000000123456781234567800206e96fe9f8b0ddcd729ba03cfafa5a27b050b39d354dd980814268dfa9a44d4c2";
+               assert_eq!(encoded_value.as_hex().to_string(), target_value);
        }
 
        #[test]
@@ -4004,7 +4036,7 @@ mod tests {
                                        <Vec<u8>>::from_hex("3045022100ee00dbf4a862463e837d7c08509de814d620e4d9830fa84818713e0fa358f145022021c3c7060c4d53fe84fd165d60208451108a778c13b92ca4c6bad439236126cc01").unwrap(),
                                        <Vec<u8>>::from_hex("028fbbf0b16f5ba5bcb5dd37cd4047ce6f726a21c06682f9ec2f52b057de1dbdb5").unwrap()]),
                        ],
-                       funding_outpoint_sig: Some(sig_1),
+                       shared_input_signature: Some(sig_1),
                };
                let encoded_value = tx_signatures.encode();
                let mut target_value = <Vec<u8>>::from_hex("0202020202020202020202020202020202020202020202020202020202020202").unwrap(); // channel_id
@@ -4233,17 +4265,19 @@ mod tests {
                        channel_id: ChannelId::from_bytes([2; 32]),
                        signature: sig_1,
                        htlc_signatures: if htlcs { vec![sig_2, sig_3, sig_4] } else { Vec::new() },
+                       batch: Some(msgs::CommitmentSignedBatch { batch_size: 3, funding_txid: Txid::from_str("c2d4449afa8d26140898dd54d3390b057ba2a5afcf03ba29d7dc0d8b9ffe966e").unwrap() }),
                        #[cfg(taproot)]
                        partial_signature_with_nonce: None,
                };
                let encoded_value = commitment_signed.encode();
-               let mut target_value = <Vec<u8>>::from_hex("0202020202020202020202020202020202020202020202020202020202020202d977cb9b53d93a6ff64bb5f1e158b4094b66e798fb12911168a3ccdf80a83096340a6a95da0ae8d9f776528eecdbb747eb6b545495a4319ed5378e35b21e073a").unwrap();
+               let mut target_value = "0202020202020202020202020202020202020202020202020202020202020202d977cb9b53d93a6ff64bb5f1e158b4094b66e798fb12911168a3ccdf80a83096340a6a95da0ae8d9f776528eecdbb747eb6b545495a4319ed5378e35b21e073a".to_string();
                if htlcs {
-                       target_value.append(&mut <Vec<u8>>::from_hex("00031735b6a427e80d5fe7cd90a2f4ee08dc9c27cda7c35a4172e5d85b12c49d4232537e98f9b1f3c5e6989a8b9644e90e8918127680dbd0d4043510840fc0f1e11a216c280b5395a2546e7e4b2663e04f811622f15a4f91e83aa2e92ba2a573c139142c54ae63072a1ec1ee7dc0c04bde5c847806172aa05c92c22ae8e308d1d2692b12cc195ce0a2d1bda6a88befa19fa07f51caa75ce83837f28965600b8aacab0855ffb0e741ec5f7c41421e9829a9d48611c8c831f71be5ea73e66594977ffd").unwrap());
+                       target_value += "00031735b6a427e80d5fe7cd90a2f4ee08dc9c27cda7c35a4172e5d85b12c49d4232537e98f9b1f3c5e6989a8b9644e90e8918127680dbd0d4043510840fc0f1e11a216c280b5395a2546e7e4b2663e04f811622f15a4f91e83aa2e92ba2a573c139142c54ae63072a1ec1ee7dc0c04bde5c847806172aa05c92c22ae8e308d1d2692b12cc195ce0a2d1bda6a88befa19fa07f51caa75ce83837f28965600b8aacab0855ffb0e741ec5f7c41421e9829a9d48611c8c831f71be5ea73e66594977ffd";
                } else {
-                       target_value.append(&mut <Vec<u8>>::from_hex("0000").unwrap());
+                       target_value += "0000";
                }
-               assert_eq!(encoded_value, target_value);
+               target_value += "002200036e96fe9f8b0ddcd729ba03cfafa5a27b050b39d354dd980814268dfa9a44d4c2"; // batch
+               assert_eq!(encoded_value.as_hex().to_string(), target_value);
        }
 
        #[test]
index 9a026d709cffa95a2dc66120ee80f0a6c6a89b29..1b75755fac2b897079d89902a4682dbe8b21c604 100644 (file)
@@ -274,7 +274,7 @@ impl ChannelMessageHandler for ErroringMessageHandler {
                ErroringMessageHandler::push_error(&self, their_node_id, msg.channel_id);
        }
        #[cfg(splicing)]
-       fn handle_splice(&self, their_node_id: &PublicKey, msg: &msgs::Splice) {
+       fn handle_splice_init(&self, their_node_id: &PublicKey, msg: &msgs::SpliceInit) {
                ErroringMessageHandler::push_error(&self, their_node_id, msg.channel_id);
        }
        #[cfg(splicing)]
@@ -1815,8 +1815,8 @@ impl<Descriptor: SocketDescriptor, CM: Deref, RM: Deref, OM: Deref, L: Deref, CM
 
                        #[cfg(splicing)]
                        // Splicing messages:
-                       wire::Message::Splice(msg) => {
-                               self.message_handler.chan_handler.handle_splice(&their_node_id, &msg);
+                       wire::Message::SpliceInit(msg) => {
+                               self.message_handler.chan_handler.handle_splice_init(&their_node_id, &msg);
                        }
                        #[cfg(splicing)]
                        wire::Message::SpliceAck(msg) => {
@@ -2154,9 +2154,9 @@ impl<Descriptor: SocketDescriptor, CM: Deref, RM: Deref, OM: Deref, L: Deref, CM
                                                                        &msg.channel_id);
                                                        self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg);
                                                }
-                                               MessageSendEvent::SendSplice { ref node_id, ref msg} => {
+                                               MessageSendEvent::SendSpliceInit { ref node_id, ref msg} => {
                                                        let logger = WithContext::from(&self.logger, Some(*node_id), Some(msg.channel_id), None);
-                                                       log_debug!(logger, "Handling SendSplice event in peer_handler for node {} for channel {}",
+                                                       log_debug!(logger, "Handling SendSpliceInit event in peer_handler for node {} for channel {}",
                                                                        log_pubkey!(node_id),
                                                                        &msg.channel_id);
                                                        self.enqueue_message(&mut *get_peer_for_forwarding!(node_id), msg);
index 55e31399ae10074d2e72f1b04a4c1c71ed1808df..4cf5e21c17367d4b4620b7edcfd488d3820d1975 100644 (file)
@@ -61,7 +61,7 @@ pub(crate) enum Message<T> where T: core::fmt::Debug + Type + TestEq {
        FundingSigned(msgs::FundingSigned),
        Stfu(msgs::Stfu),
        #[cfg(splicing)]
-       Splice(msgs::Splice),
+       SpliceInit(msgs::SpliceInit),
        #[cfg(splicing)]
        SpliceAck(msgs::SpliceAck),
        #[cfg(splicing)]
@@ -119,7 +119,7 @@ impl<T> Writeable for Message<T> where T: core::fmt::Debug + Type + TestEq {
                        &Message::FundingSigned(ref msg) => msg.write(writer),
                        &Message::Stfu(ref msg) => msg.write(writer),
                        #[cfg(splicing)]
-                       &Message::Splice(ref msg) => msg.write(writer),
+                       &Message::SpliceInit(ref msg) => msg.write(writer),
                        #[cfg(splicing)]
                        &Message::SpliceAck(ref msg) => msg.write(writer),
                        #[cfg(splicing)]
@@ -177,7 +177,7 @@ impl<T> Type for Message<T> where T: core::fmt::Debug + Type + TestEq {
                        &Message::FundingSigned(ref msg) => msg.type_id(),
                        &Message::Stfu(ref msg) => msg.type_id(),
                        #[cfg(splicing)]
-                       &Message::Splice(ref msg) => msg.type_id(),
+                       &Message::SpliceInit(ref msg) => msg.type_id(),
                        #[cfg(splicing)]
                        &Message::SpliceAck(ref msg) => msg.type_id(),
                        #[cfg(splicing)]
@@ -280,8 +280,8 @@ fn do_read<R: io::Read, T, H: core::ops::Deref>(buffer: &mut R, message_type: u1
                        Ok(Message::FundingSigned(Readable::read(buffer)?))
                },
                #[cfg(splicing)]
-               msgs::Splice::TYPE => {
-                       Ok(Message::Splice(Readable::read(buffer)?))
+               msgs::SpliceInit::TYPE => {
+                       Ok(Message::SpliceInit(Readable::read(buffer)?))
                },
                msgs::Stfu::TYPE => {
                        Ok(Message::Stfu(Readable::read(buffer)?))
@@ -504,13 +504,13 @@ impl Encode for msgs::AcceptChannelV2 {
        const TYPE: u16 = 65;
 }
 
-impl Encode for msgs::Splice {
-       // TODO(splicing) Double check with finalized spec; draft spec contains 74, which is probably wrong as it is used by tx_Abort; CLN uses 75
-       const TYPE: u16 = 75;
+impl Encode for msgs::SpliceInit {
+       // TODO(splicing) Double check with finalized spec; draft spec contains 80; previously it was 74 (conflict with tx_abort); CLN used 75
+       const TYPE: u16 = 80;
 }
 
 impl Encode for msgs::SpliceAck {
-       const TYPE: u16 = 76;
+       const TYPE: u16 = 81;
 }
 
 impl Encode for msgs::SpliceLocked {
index 85600190db3df5cd0015ea4b1c809104f36a2292..21012bd7fed2f471c4601b6047feb17cc3107c8a 100644 (file)
@@ -1633,7 +1633,7 @@ pub type SimpleArcOnionMessenger<M, T, F, L> = OnionMessenger<
        Arc<SimpleArcChannelManager<M, T, F, L>>,
        Arc<DefaultMessageRouter<Arc<NetworkGraph<Arc<L>>>, Arc<L>, Arc<KeysManager>>>,
        Arc<SimpleArcChannelManager<M, T, F, L>>,
-       IgnoringMessageHandler,
+       Arc<SimpleArcChannelManager<M, T, F, L>>,
        IgnoringMessageHandler
 >;
 
@@ -1654,7 +1654,7 @@ pub type SimpleRefOnionMessenger<
        &'i SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, M, T, F, L>,
        &'j DefaultMessageRouter<&'g NetworkGraph<&'b L>, &'b L, &'a KeysManager>,
        &'i SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, M, T, F, L>,
-       IgnoringMessageHandler,
+       &'i SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, M, T, F, L>,
        IgnoringMessageHandler
 >;
 
index 22df8ec31c6f1cee924641e6dff51c9fb6f149c7..1006113f7a31a7a293ef2563110548d6494d5bda 100644 (file)
@@ -809,8 +809,8 @@ impl msgs::ChannelMessageHandler for TestChannelMessageHandler {
                self.received_msg(wire::Message::Stfu(msg.clone()));
        }
        #[cfg(splicing)]
-       fn handle_splice(&self, _their_node_id: &PublicKey, msg: &msgs::Splice) {
-               self.received_msg(wire::Message::Splice(msg.clone()));
+       fn handle_splice_init(&self, _their_node_id: &PublicKey, msg: &msgs::SpliceInit) {
+               self.received_msg(wire::Message::SpliceInit(msg.clone()));
        }
        #[cfg(splicing)]
        fn handle_splice_ack(&self, _their_node_id: &PublicKey, msg: &msgs::SpliceAck) {