Skip to content

Commit ad09a2f

Browse files
authored
Merge pull request #794 from galderz/t_opt_shutdown_anysegwit_780
Add support for `opt_shutdown_anysegwit` feature #780
2 parents d6f41d3 + 24ed1dc commit ad09a2f

File tree

9 files changed

+215
-48
lines changed

9 files changed

+215
-48
lines changed

lightning-net-tokio/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ mod tests {
551551
fn handle_funding_created(&self, _their_node_id: &PublicKey, _msg: &FundingCreated) {}
552552
fn handle_funding_signed(&self, _their_node_id: &PublicKey, _msg: &FundingSigned) {}
553553
fn handle_funding_locked(&self, _their_node_id: &PublicKey, _msg: &FundingLocked) {}
554-
fn handle_shutdown(&self, _their_node_id: &PublicKey, _msg: &Shutdown) {}
554+
fn handle_shutdown(&self, _their_node_id: &PublicKey, _their_features: &InitFeatures, _msg: &Shutdown) {}
555555
fn handle_closing_signed(&self, _their_node_id: &PublicKey, _msg: &ClosingSigned) {}
556556
fn handle_update_add_htlc(&self, _their_node_id: &PublicKey, _msg: &UpdateAddHTLC) {}
557557
fn handle_update_fulfill_htlc(&self, _their_node_id: &PublicKey, _msg: &UpdateFulfillHTLC) {}

lightning/src/ln/channel.rs

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ use std::ops::Deref;
4545
#[cfg(any(test, feature = "fuzztarget"))]
4646
use std::sync::Mutex;
4747
use bitcoin::hashes::hex::ToHex;
48+
use bitcoin::blockdata::opcodes::all::OP_PUSHBYTES_0;
4849

4950
#[cfg(test)]
5051
pub struct ChannelValueStat {
@@ -737,15 +738,14 @@ impl<Signer: Sign> Channel<Signer> {
737738
let counterparty_shutdown_scriptpubkey = if their_features.supports_upfront_shutdown_script() {
738739
match &msg.shutdown_scriptpubkey {
739740
&OptionalField::Present(ref script) => {
740-
// Peer is signaling upfront_shutdown and has provided a non-accepted scriptpubkey format. We enforce it while receiving shutdown msg
741-
if script.is_p2pkh() || script.is_p2sh() || script.is_v0_p2wsh() || script.is_v0_p2wpkh() {
742-
Some(script.clone())
743741
// Peer is signaling upfront_shutdown and has opt-out with a 0-length script. We don't enforce anything
744-
} else if script.len() == 0 {
742+
if script.len() == 0 {
745743
None
746744
// Peer is signaling upfront_shutdown and has provided a non-accepted scriptpubkey format. Fail the channel
747-
} else {
745+
} else if is_unsupported_shutdown_script(&their_features, script) {
748746
return Err(ChannelError::Close(format!("Peer is signaling upfront_shutdown but has provided a non-accepted scriptpubkey format. script: ({})", script.to_bytes().to_hex())));
747+
} else {
748+
Some(script.clone())
749749
}
750750
},
751751
// Peer is signaling upfront shutdown but don't opt-out with correct mechanism (a.k.a 0-length script). Peer looks buggy, we fail the channel
@@ -1439,15 +1439,14 @@ impl<Signer: Sign> Channel<Signer> {
14391439
let counterparty_shutdown_scriptpubkey = if their_features.supports_upfront_shutdown_script() {
14401440
match &msg.shutdown_scriptpubkey {
14411441
&OptionalField::Present(ref script) => {
1442-
// Peer is signaling upfront_shutdown and has provided a non-accepted scriptpubkey format. We enforce it while receiving shutdown msg
1443-
if script.is_p2pkh() || script.is_p2sh() || script.is_v0_p2wsh() || script.is_v0_p2wpkh() {
1444-
Some(script.clone())
14451442
// Peer is signaling upfront_shutdown and has opt-out with a 0-length script. We don't enforce anything
1446-
} else if script.len() == 0 {
1443+
if script.len() == 0 {
14471444
None
14481445
// Peer is signaling upfront_shutdown and has provided a non-accepted scriptpubkey format. Fail the channel
1446+
} else if is_unsupported_shutdown_script(&their_features, script) {
1447+
return Err(ChannelError::Close(format!("Peer is signaling upfront_shutdown but has provided a non-accepted scriptpubkey format. script: ({})", script.to_bytes().to_hex())));
14491448
} else {
1450-
return Err(ChannelError::Close(format!("Peer is signaling upfront_shutdown but has provided a non-accepted scriptpubkey format. scriptpubkey: ({})", script.to_bytes().to_hex())));
1449+
Some(script.clone())
14511450
}
14521451
},
14531452
// Peer is signaling upfront shutdown but don't opt-out with correct mechanism (a.k.a 0-length script). Peer looks buggy, we fail the channel
@@ -3066,7 +3065,7 @@ impl<Signer: Sign> Channel<Signer> {
30663065
})
30673066
}
30683067

3069-
pub fn shutdown<F: Deref>(&mut self, fee_estimator: &F, msg: &msgs::Shutdown) -> Result<(Option<msgs::Shutdown>, Option<msgs::ClosingSigned>, Vec<(HTLCSource, PaymentHash)>), ChannelError>
3068+
pub fn shutdown<F: Deref>(&mut self, fee_estimator: &F, their_features: &InitFeatures, msg: &msgs::Shutdown) -> Result<(Option<msgs::Shutdown>, Option<msgs::ClosingSigned>, Vec<(HTLCSource, PaymentHash)>), ChannelError>
30703069
where F::Target: FeeEstimator
30713070
{
30723071
if self.channel_state & (ChannelState::PeerDisconnected as u32) == ChannelState::PeerDisconnected as u32 {
@@ -3085,14 +3084,7 @@ impl<Signer: Sign> Channel<Signer> {
30853084
}
30863085
assert_eq!(self.channel_state & ChannelState::ShutdownComplete as u32, 0);
30873086

3088-
// BOLT 2 says we must only send a scriptpubkey of certain standard forms, which are up to
3089-
// 34 bytes in length, so don't let the remote peer feed us some super fee-heavy script.
3090-
if self.is_outbound() && msg.scriptpubkey.len() > 34 {
3091-
return Err(ChannelError::Close(format!("Got counterparty shutdown_scriptpubkey ({}) of absurd length from remote peer", msg.scriptpubkey.to_bytes().to_hex())));
3092-
}
3093-
3094-
//Check counterparty_shutdown_scriptpubkey form as BOLT says we must
3095-
if !msg.scriptpubkey.is_p2pkh() && !msg.scriptpubkey.is_p2sh() && !msg.scriptpubkey.is_v0_p2wpkh() && !msg.scriptpubkey.is_v0_p2wsh() {
3087+
if is_unsupported_shutdown_script(&their_features, &msg.scriptpubkey) {
30963088
return Err(ChannelError::Close(format!("Got a nonstandard scriptpubkey ({}) from remote peer", msg.scriptpubkey.to_bytes().to_hex())));
30973089
}
30983090

@@ -4217,6 +4209,24 @@ impl<Signer: Sign> Channel<Signer> {
42174209
}
42184210
}
42194211

4212+
fn is_unsupported_shutdown_script(their_features: &InitFeatures, script: &Script) -> bool {
4213+
// We restrain shutdown scripts to standards forms to avoid transactions not propagating on the p2p tx-relay network
4214+
4215+
// BOLT 2 says we must only send a scriptpubkey of certain standard forms,
4216+
// which for a a BIP-141-compliant witness program is at max 42 bytes in length.
4217+
// So don't let the remote peer feed us some super fee-heavy script.
4218+
let is_script_too_long = script.len() > 42;
4219+
if is_script_too_long {
4220+
return true;
4221+
}
4222+
4223+
if their_features.supports_shutdown_anysegwit() && script.is_witness_program() && script.as_bytes()[0] != OP_PUSHBYTES_0.into_u8() {
4224+
return false;
4225+
}
4226+
4227+
return !script.is_p2pkh() && !script.is_p2sh() && !script.is_v0_p2wpkh() && !script.is_v0_p2wsh()
4228+
}
4229+
42204230
const SERIALIZATION_VERSION: u8 = 1;
42214231
const MIN_SERIALIZATION_VERSION: u8 = 1;
42224232

lightning/src/ln/channelmanager.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2507,7 +2507,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
25072507
}
25082508
}
25092509

2510-
fn internal_shutdown(&self, counterparty_node_id: &PublicKey, msg: &msgs::Shutdown) -> Result<(), MsgHandleErrInternal> {
2510+
fn internal_shutdown(&self, counterparty_node_id: &PublicKey, their_features: &InitFeatures, msg: &msgs::Shutdown) -> Result<(), MsgHandleErrInternal> {
25112511
let (mut dropped_htlcs, chan_option) = {
25122512
let mut channel_state_lock = self.channel_state.lock().unwrap();
25132513
let channel_state = &mut *channel_state_lock;
@@ -2517,7 +2517,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
25172517
if chan_entry.get().get_counterparty_node_id() != *counterparty_node_id {
25182518
return Err(MsgHandleErrInternal::send_err_msg_no_close("Got a message for a channel from the wrong node!".to_owned(), msg.channel_id));
25192519
}
2520-
let (shutdown, closing_signed, dropped_htlcs) = try_chan_entry!(self, chan_entry.get_mut().shutdown(&self.fee_estimator, &msg), channel_state, chan_entry);
2520+
let (shutdown, closing_signed, dropped_htlcs) = try_chan_entry!(self, chan_entry.get_mut().shutdown(&self.fee_estimator, &their_features, &msg), channel_state, chan_entry);
25212521
if let Some(msg) = shutdown {
25222522
channel_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown {
25232523
node_id: counterparty_node_id.clone(),
@@ -3351,9 +3351,9 @@ impl<Signer: Sign, M: Deref + Sync + Send, T: Deref + Sync + Send, K: Deref + Sy
33513351
let _ = handle_error!(self, self.internal_funding_locked(counterparty_node_id, msg), *counterparty_node_id);
33523352
}
33533353

3354-
fn handle_shutdown(&self, counterparty_node_id: &PublicKey, msg: &msgs::Shutdown) {
3354+
fn handle_shutdown(&self, counterparty_node_id: &PublicKey, their_features: &InitFeatures, msg: &msgs::Shutdown) {
33553355
let _persistence_guard = PersistenceNotifierGuard::new(&self.total_consistency_lock, &self.persistence_notifier);
3356-
let _ = handle_error!(self, self.internal_shutdown(counterparty_node_id, msg), *counterparty_node_id);
3356+
let _ = handle_error!(self, self.internal_shutdown(counterparty_node_id, their_features, msg), *counterparty_node_id);
33573357
}
33583358

33593359
fn handle_closing_signed(&self, counterparty_node_id: &PublicKey, msg: &msgs::ClosingSigned) {

lightning/src/ln/features.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ mod sealed {
100100
StaticRemoteKey,
101101
// Byte 2
102102
,
103+
// Byte 3
104+
,
103105
],
104106
optional_features: [
105107
// Byte 0
@@ -108,6 +110,8 @@ mod sealed {
108110
VariableLengthOnion | PaymentSecret,
109111
// Byte 2
110112
BasicMPP,
113+
// Byte 3
114+
ShutdownAnySegwit,
111115
],
112116
});
113117
define_context!(NodeContext {
@@ -118,6 +122,8 @@ mod sealed {
118122
StaticRemoteKey,
119123
// Byte 2
120124
,
125+
// Byte 3
126+
,
121127
],
122128
optional_features: [
123129
// Byte 0
@@ -126,6 +132,8 @@ mod sealed {
126132
VariableLengthOnion | PaymentSecret,
127133
// Byte 2
128134
BasicMPP,
135+
// Byte 3
136+
ShutdownAnySegwit,
129137
],
130138
});
131139
define_context!(ChannelContext {
@@ -252,6 +260,8 @@ mod sealed {
252260
"Feature flags for `payment_secret`.");
253261
define_feature!(17, BasicMPP, [InitContext, NodeContext],
254262
"Feature flags for `basic_mpp`.");
263+
define_feature!(27, ShutdownAnySegwit, [InitContext, NodeContext],
264+
"Feature flags for `opt_shutdown_anysegwit`.");
255265

256266
#[cfg(test)]
257267
define_context!(TestingContext {
@@ -549,6 +559,17 @@ impl<T: sealed::BasicMPP> Features<T> {
549559
}
550560
}
551561

562+
impl<T: sealed::ShutdownAnySegwit> Features<T> {
563+
pub(crate) fn supports_shutdown_anysegwit(&self) -> bool {
564+
<T as sealed::ShutdownAnySegwit>::supports_feature(&self.flags)
565+
}
566+
#[cfg(test)]
567+
pub(crate) fn clear_shutdown_anysegwit(mut self) -> Self {
568+
<T as sealed::ShutdownAnySegwit>::clear_bits(&mut self.flags);
569+
self
570+
}
571+
}
572+
552573
impl<T: sealed::Context> Writeable for Features<T> {
553574
fn write<W: Writer>(&self, w: &mut W) -> Result<(), ::std::io::Error> {
554575
w.size_hint(self.flags.len() + 2);
@@ -619,6 +640,9 @@ mod tests {
619640
assert!(!InitFeatures::known().requires_basic_mpp());
620641
assert!(!NodeFeatures::known().requires_basic_mpp());
621642

643+
assert!(InitFeatures::known().supports_shutdown_anysegwit());
644+
assert!(NodeFeatures::known().supports_shutdown_anysegwit());
645+
622646
let mut init_features = InitFeatures::known();
623647
assert!(init_features.initial_routing_sync());
624648
init_features.clear_initial_routing_sync();
@@ -657,10 +681,12 @@ mod tests {
657681
// - option_data_loss_protect
658682
// - var_onion_optin | static_remote_key (req) | payment_secret
659683
// - basic_mpp
660-
assert_eq!(node_features.flags.len(), 3);
684+
// - opt_shutdown_anysegwit
685+
assert_eq!(node_features.flags.len(), 4);
661686
assert_eq!(node_features.flags[0], 0b00000010);
662687
assert_eq!(node_features.flags[1], 0b10010010);
663688
assert_eq!(node_features.flags[2], 0b00000010);
689+
assert_eq!(node_features.flags[3], 0b00001000);
664690
}
665691

666692
// Check that cleared flags are kept blank when converting back:

lightning/src/ln/functional_test_utils.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ pub fn close_channel<'a, 'b, 'c>(outbound_node: &Node<'a, 'b, 'c>, inbound_node:
604604
let (tx_a, tx_b);
605605

606606
node_a.close_channel(channel_id).unwrap();
607-
node_b.handle_shutdown(&node_a.get_our_node_id(), &get_event_msg!(struct_a, MessageSendEvent::SendShutdown, node_b.get_our_node_id()));
607+
node_b.handle_shutdown(&node_a.get_our_node_id(), &InitFeatures::known(), &get_event_msg!(struct_a, MessageSendEvent::SendShutdown, node_b.get_our_node_id()));
608608

609609
let events_1 = node_b.get_and_clear_pending_msg_events();
610610
assert!(events_1.len() >= 1);
@@ -629,7 +629,7 @@ pub fn close_channel<'a, 'b, 'c>(outbound_node: &Node<'a, 'b, 'c>, inbound_node:
629629
})
630630
};
631631

632-
node_a.handle_shutdown(&node_b.get_our_node_id(), &shutdown_b);
632+
node_a.handle_shutdown(&node_b.get_our_node_id(), &InitFeatures::known(), &shutdown_b);
633633
let (as_update, bs_update) = if close_inbound_first {
634634
assert!(node_a.get_and_clear_pending_msg_events().is_empty());
635635
node_a.handle_closing_signed(&node_b.get_our_node_id(), &closing_signed_b.unwrap());

0 commit comments

Comments
 (0)