Skip to content

Commit 652b7ae

Browse files
committed
Implement support for accepting V2 channels
1 parent 6e22400 commit 652b7ae

File tree

4 files changed

+819
-182
lines changed

4 files changed

+819
-182
lines changed

lightning/src/events/mod.rs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,20 @@ impl_writeable_tlv_based_enum_upgradable!(PaymentFailureReason,
589589
(10, UnexpectedError) => {},
590590
);
591591

592+
/// Used to indicate the kind of funding for this channel by the channel acceptor (us).
593+
///
594+
/// Allows the differentiation between a request for a dual-funded and non-dual-funded channel.
595+
#[derive(Clone, Debug, PartialEq, Eq)]
596+
pub enum InboundChannelFunds {
597+
/// For a non-dual-funded channel, the `push_msat` value from the channel initiator to us.
598+
PushMsat(u64),
599+
/// Indicates the open request is for a dual funded channel.
600+
///
601+
/// Note that these channels do not support starting with initial funds pushed from the counterparty,
602+
/// who is the channel opener in this case.
603+
DualFunded,
604+
}
605+
592606
/// An Event which you should probably take some action in response to.
593607
///
594608
/// Note that while Writeable and Readable are implemented for Event, you probably shouldn't use
@@ -1293,9 +1307,14 @@ pub enum Event {
12931307
},
12941308
/// Indicates a request to open a new channel by a peer.
12951309
///
1296-
/// To accept the request, call [`ChannelManager::accept_inbound_channel`]. To reject the request,
1297-
/// call [`ChannelManager::force_close_without_broadcasting_txn`]. Note that a ['ChannelClosed`]
1298-
/// event will _not_ be triggered if the channel is rejected.
1310+
/// If `channel_negotiation_type` is `InboundChannelFunds::DualFunded`, this indicates that the peer wishes to
1311+
/// open a dual-funded channel. Otherwise, this field will be `InboundChannelFunds::PushMsats`,
1312+
/// indicating the `push_msats` value our peer is pushing to us for a non-dual-funded channel.
1313+
///
1314+
/// To accept the request (and in the case of a dual-funded channel, not contribute funds),
1315+
/// call [`ChannelManager::accept_inbound_channel`].
1316+
/// To reject the request, call [`ChannelManager::force_close_without_broadcasting_txn`].
1317+
/// Note that a ['ChannelClosed`] event will _not_ be triggered if the channel is rejected.
12991318
///
13001319
/// The event is only triggered when a new open channel request is received and the
13011320
/// [`UserConfig::manually_accept_inbound_channels`] config flag is set to true.
@@ -1329,8 +1348,10 @@ pub enum Event {
13291348
counterparty_node_id: PublicKey,
13301349
/// The channel value of the requested channel.
13311350
funding_satoshis: u64,
1332-
/// Our starting balance in the channel if the request is accepted, in milli-satoshi.
1333-
push_msat: u64,
1351+
/// If `channel_negotiation_type` is `InboundChannelFunds::DualFunded`, this indicates that the peer wishes to
1352+
/// open a dual-funded channel. Otherwise, this field will be `InboundChannelFunds::PushMsats`,
1353+
/// indicating the `push_msats` value our peer is pushing to us for a non-dual-funded channel.
1354+
channel_negotiation_type: InboundChannelFunds,
13341355
/// The features that this channel will operate with. If you reject the channel, a
13351356
/// well-behaved counterparty may automatically re-attempt the channel with a new set of
13361357
/// feature flags.

lightning/src/ln/channel.rs

Lines changed: 213 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@
1010
use bitcoin::amount::Amount;
1111
use bitcoin::constants::ChainHash;
1212
use bitcoin::script::{Script, ScriptBuf, Builder};
13-
use bitcoin::transaction::Transaction;
13+
use bitcoin::transaction::{Transaction, TxIn, TxOut};
1414
use bitcoin::sighash;
1515
use bitcoin::sighash::EcdsaSighashType;
1616
use bitcoin::consensus::encode;
17+
use bitcoin::absolute::LockTime;
1718

1819
use bitcoin::hashes::Hash;
1920
use bitcoin::hashes::sha256::Hash as Sha256;
@@ -27,7 +28,11 @@ use bitcoin::secp256k1;
2728

2829
use crate::ln::types::{ChannelId, PaymentPreimage, PaymentHash};
2930
use crate::ln::features::{ChannelTypeFeatures, InitFeatures};
30-
use crate::ln::interactivetxs::InteractiveTxConstructor;
31+
use crate::ln::interactivetxs::{
32+
estimate_input_weight, get_output_weight, HandleTxCompleteResult,
33+
InteractiveTxConstructor, InteractiveTxConstructorArgs, InteractiveTxMessageSend,
34+
InteractiveTxMessageSendResult, TX_COMMON_FIELDS_WEIGHT,
35+
};
3136
use crate::ln::msgs;
3237
use crate::ln::msgs::{ClosingSigned, ClosingSignedFeeRange, DecodeError};
3338
use crate::ln::script::{self, ShutdownScript};
@@ -44,14 +49,14 @@ use crate::ln::chan_utils::{
4449
use crate::ln::chan_utils;
4550
use crate::ln::onion_utils::HTLCFailReason;
4651
use crate::chain::BestBlock;
47-
use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, LowerBoundedFeeEstimator};
52+
use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, LowerBoundedFeeEstimator, fee_for_weight};
4853
use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, ChannelMonitorUpdateStep, LATENCY_GRACE_PERIOD_BLOCKS, CLOSED_CHANNEL_UPDATE_ID};
4954
use crate::chain::transaction::{OutPoint, TransactionData};
5055
use crate::sign::ecdsa::EcdsaChannelSigner;
5156
use crate::sign::{EntropySource, ChannelSigner, SignerProvider, NodeSigner, Recipient};
5257
use crate::events::ClosureReason;
5358
use crate::routing::gossip::NodeId;
54-
use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer};
59+
use crate::util::ser::{Readable, ReadableArgs, TransactionU16LenLimited, Writeable, Writer};
5560
use crate::util::logger::{Logger, Record, WithContext};
5661
use crate::util::errors::APIError;
5762
use crate::util::config::{UserConfig, ChannelConfig, LegacyChannelConfig, ChannelHandshakeConfig, ChannelHandshakeLimits, MaxDustHTLCExposure};
@@ -1489,6 +1494,109 @@ pub(super) struct ChannelContext<SP: Deref> where SP::Target: SignerProvider {
14891494
blocked_monitor_updates: Vec<PendingChannelMonitorUpdate>,
14901495
}
14911496

1497+
pub(super) trait InteractivelyFunded<SP: Deref> where SP::Target: SignerProvider {
1498+
fn context(&self) -> &ChannelContext<SP>;
1499+
1500+
fn context_mut(&mut self) -> &mut ChannelContext<SP>;
1501+
1502+
fn interactive_tx_constructor_mut(&mut self) -> &mut Option<InteractiveTxConstructor>;
1503+
1504+
fn dual_funding_context(&self) -> &DualFundingChannelContext;
1505+
1506+
fn set_interactive_tx_constructor(&mut self, interactive_tx_constructor: InteractiveTxConstructor);
1507+
1508+
fn tx_add_input(&mut self, msg: &msgs::TxAddInput) -> InteractiveTxMessageSendResult {
1509+
InteractiveTxMessageSendResult(match self.interactive_tx_constructor_mut() {
1510+
Some(ref mut tx_constructor) => tx_constructor.handle_tx_add_input(msg).map_err(
1511+
|reason| reason.into_tx_abort_msg(self.context().channel_id())),
1512+
None => Err(msgs::TxAbort {
1513+
channel_id: self.context().channel_id(),
1514+
data: b"No interactive transaction negotiation in progress".to_vec()
1515+
}),
1516+
})
1517+
}
1518+
1519+
fn tx_add_output(&mut self, msg: &msgs::TxAddOutput)-> InteractiveTxMessageSendResult {
1520+
InteractiveTxMessageSendResult(match self.interactive_tx_constructor_mut() {
1521+
Some(ref mut tx_constructor) => tx_constructor.handle_tx_add_output(msg).map_err(
1522+
|reason| reason.into_tx_abort_msg(self.context().channel_id())),
1523+
None => Err(msgs::TxAbort {
1524+
channel_id: self.context().channel_id(),
1525+
data: b"No interactive transaction negotiation in progress".to_vec()
1526+
}),
1527+
})
1528+
}
1529+
1530+
fn tx_remove_input(&mut self, msg: &msgs::TxRemoveInput)-> InteractiveTxMessageSendResult {
1531+
InteractiveTxMessageSendResult(match self.interactive_tx_constructor_mut() {
1532+
Some(ref mut tx_constructor) => tx_constructor.handle_tx_remove_input(msg).map_err(
1533+
|reason| reason.into_tx_abort_msg(self.context().channel_id())),
1534+
None => Err(msgs::TxAbort {
1535+
channel_id: self.context().channel_id(),
1536+
data: b"No interactive transaction negotiation in progress".to_vec()
1537+
}),
1538+
})
1539+
}
1540+
1541+
fn tx_remove_output(&mut self, msg: &msgs::TxRemoveOutput)-> InteractiveTxMessageSendResult {
1542+
InteractiveTxMessageSendResult(match self.interactive_tx_constructor_mut() {
1543+
Some(ref mut tx_constructor) => tx_constructor.handle_tx_remove_output(msg).map_err(
1544+
|reason| reason.into_tx_abort_msg(self.context().channel_id())),
1545+
None => Err(msgs::TxAbort {
1546+
channel_id: self.context().channel_id(),
1547+
data: b"No interactive transaction negotiation in progress".to_vec()
1548+
}),
1549+
})
1550+
}
1551+
1552+
fn tx_complete(&mut self, msg: &msgs::TxComplete) -> HandleTxCompleteResult {
1553+
HandleTxCompleteResult(match self.interactive_tx_constructor_mut() {
1554+
Some(ref mut tx_constructor) => tx_constructor.handle_tx_complete(msg).map_err(
1555+
|reason| reason.into_tx_abort_msg(self.context().channel_id())),
1556+
None => Err(msgs::TxAbort {
1557+
channel_id: self.context().channel_id(),
1558+
data: b"No interactive transaction negotiation in progress".to_vec()
1559+
}),
1560+
})
1561+
}
1562+
}
1563+
1564+
impl<SP: Deref> InteractivelyFunded<SP> for OutboundV2Channel<SP> where SP::Target: SignerProvider {
1565+
fn context(&self) -> &ChannelContext<SP> {
1566+
&self.context
1567+
}
1568+
fn context_mut(&mut self) -> &mut ChannelContext<SP> {
1569+
&mut self.context
1570+
}
1571+
fn dual_funding_context(&self) -> &DualFundingChannelContext {
1572+
&self.dual_funding_context
1573+
}
1574+
fn interactive_tx_constructor_mut(&mut self) -> &mut Option<InteractiveTxConstructor> {
1575+
&mut self.interactive_tx_constructor
1576+
}
1577+
fn set_interactive_tx_constructor(&mut self, interactive_tx_constructor: InteractiveTxConstructor) {
1578+
self.interactive_tx_constructor = Some(interactive_tx_constructor);
1579+
}
1580+
}
1581+
1582+
impl<SP: Deref> InteractivelyFunded<SP> for InboundV2Channel<SP> where SP::Target: SignerProvider {
1583+
fn context(&self) -> &ChannelContext<SP> {
1584+
&self.context
1585+
}
1586+
fn context_mut(&mut self) -> &mut ChannelContext<SP> {
1587+
&mut self.context
1588+
}
1589+
fn dual_funding_context(&self) -> &DualFundingChannelContext {
1590+
&self.dual_funding_context
1591+
}
1592+
fn interactive_tx_constructor_mut(&mut self) -> &mut Option<InteractiveTxConstructor> {
1593+
&mut self.interactive_tx_constructor
1594+
}
1595+
fn set_interactive_tx_constructor(&mut self, interactive_tx_constructor: InteractiveTxConstructor) {
1596+
self.interactive_tx_constructor = Some(interactive_tx_constructor);
1597+
}
1598+
}
1599+
14921600
impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
14931601
fn new_for_inbound_channel<'a, ES: Deref, F: Deref, L: Deref>(
14941602
fee_estimator: &'a LowerBoundedFeeEstimator<F>,
@@ -3616,6 +3724,16 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
36163724
self.channel_transaction_parameters.channel_type_features = self.channel_type.clone();
36173725
Ok(())
36183726
}
3727+
3728+
// Interactive transaction construction
3729+
3730+
pub fn tx_signatures(&self, msg: &msgs::TxSignatures) -> Result<InteractiveTxMessageSend, ChannelError> {
3731+
todo!();
3732+
}
3733+
3734+
pub fn tx_abort(&self, msg: &msgs::TxAbort) -> Result<InteractiveTxMessageSend, ChannelError> {
3735+
todo!();
3736+
}
36193737
}
36203738

36213739
// Internal utility functions for channels
@@ -3673,6 +3791,42 @@ fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satos
36733791
cmp::min(channel_value_satoshis, cmp::max(q, dust_limit_satoshis))
36743792
}
36753793

3794+
pub(super) fn calculate_our_funding_satoshis(
3795+
is_initiator: bool, funding_inputs: &[(TxIn, TransactionU16LenLimited)],
3796+
funding_outputs: &[TxOut], funding_feerate_sat_per_1000_weight: u32,
3797+
holder_dust_limit_satoshis: u64,
3798+
) -> Result<u64, APIError> {
3799+
let mut total_input_satoshis = 0u64;
3800+
let mut our_contributed_weight = 0u64;
3801+
3802+
for (idx, input) in funding_inputs.iter().enumerate() {
3803+
if let Some(output) = input.1.as_transaction().output.get(input.0.previous_output.vout as usize) {
3804+
total_input_satoshis = total_input_satoshis.saturating_add(output.value.to_sat());
3805+
our_contributed_weight = our_contributed_weight.saturating_add(estimate_input_weight(output).to_wu());
3806+
} else {
3807+
return Err(APIError::APIMisuseError {
3808+
err: format!("Transaction with txid {} does not have an output with vout of {} corresponding to TxIn at funding_inputs[{}]",
3809+
input.1.as_transaction().compute_txid(), input.0.previous_output.vout, idx) });
3810+
}
3811+
}
3812+
our_contributed_weight = our_contributed_weight.saturating_add(funding_outputs.iter().fold(0u64, |weight, txout| {
3813+
weight.saturating_add(get_output_weight(&txout.script_pubkey).to_wu())
3814+
}));
3815+
3816+
// If we are the initiator, we must pay for weight of all common fields in the funding transaction.
3817+
if is_initiator {
3818+
our_contributed_weight = our_contributed_weight.saturating_add(TX_COMMON_FIELDS_WEIGHT);
3819+
}
3820+
3821+
let funding_satoshis = total_input_satoshis
3822+
.saturating_sub(fee_for_weight(funding_feerate_sat_per_1000_weight, our_contributed_weight));
3823+
if funding_satoshis < holder_dust_limit_satoshis {
3824+
Ok(0)
3825+
} else {
3826+
Ok(funding_satoshis)
3827+
}
3828+
}
3829+
36763830
/// Context for dual-funded channels.
36773831
pub(super) struct DualFundingChannelContext {
36783832
/// The amount in satoshis we will be contributing to the channel.
@@ -3681,9 +3835,15 @@ pub(super) struct DualFundingChannelContext {
36813835
pub their_funding_satoshis: u64,
36823836
/// The funding transaction locktime suggested by the initiator. If set by us, it is always set
36833837
/// to the current block height to align incentives against fee-sniping.
3684-
pub funding_tx_locktime: u32,
3838+
pub funding_tx_locktime: LockTime,
36853839
/// The feerate set by the initiator to be used for the funding transaction.
36863840
pub funding_feerate_sat_per_1000_weight: u32,
3841+
/// The funding inputs we will be contributing to the channel.
3842+
///
3843+
/// Note that the `our_funding_satoshis` field is equal to the total value of `our_funding_inputs`
3844+
/// minus any fees paid for our contributed weight. This means that change will never be generated
3845+
/// and the maximum value possible will go towards funding the channel.
3846+
pub our_funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>,
36873847
}
36883848

36893849
// Holder designates channel data owned for the benefit of the user client.
@@ -8244,8 +8404,9 @@ impl<SP: Deref> OutboundV2Channel<SP> where SP::Target: SignerProvider {
82448404
pub fn new<ES: Deref, F: Deref, L: Deref>(
82458405
fee_estimator: &LowerBoundedFeeEstimator<F>, entropy_source: &ES, signer_provider: &SP,
82468406
counterparty_node_id: PublicKey, their_features: &InitFeatures, funding_satoshis: u64,
8247-
user_id: u128, config: &UserConfig, current_chain_height: u32, outbound_scid_alias: u64,
8248-
funding_confirmation_target: ConfirmationTarget, logger: L,
8407+
funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>, user_id: u128, config: &UserConfig,
8408+
current_chain_height: u32, outbound_scid_alias: u64, funding_confirmation_target: ConfirmationTarget,
8409+
logger: L,
82498410
) -> Result<OutboundV2Channel<SP>, APIError>
82508411
where ES::Target: EntropySource,
82518412
F::Target: FeeEstimator,
@@ -8261,7 +8422,11 @@ impl<SP: Deref> OutboundV2Channel<SP> where SP::Target: SignerProvider {
82618422
funding_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS);
82628423

82638424
let funding_feerate_sat_per_1000_weight = fee_estimator.bounded_sat_per_1000_weight(funding_confirmation_target);
8264-
let funding_tx_locktime = current_chain_height;
8425+
let funding_tx_locktime = LockTime::from_height(current_chain_height)
8426+
.map_err(|_| APIError::APIMisuseError {
8427+
err: format!(
8428+
"Provided current chain height of {} doesn't make sense for a height-based timelock for the funding transaction",
8429+
current_chain_height) })?;
82658430

82668431
let chan = Self {
82678432
context: ChannelContext::new_for_outbound_channel(
@@ -8289,6 +8454,7 @@ impl<SP: Deref> OutboundV2Channel<SP> where SP::Target: SignerProvider {
82898454
their_funding_satoshis: 0,
82908455
funding_tx_locktime,
82918456
funding_feerate_sat_per_1000_weight,
8457+
our_funding_inputs: funding_inputs,
82928458
},
82938459
interactive_tx_constructor: None,
82948460
};
@@ -8353,7 +8519,7 @@ impl<SP: Deref> OutboundV2Channel<SP> where SP::Target: SignerProvider {
83538519
},
83548520
funding_feerate_sat_per_1000_weight: self.context.feerate_per_kw,
83558521
second_per_commitment_point,
8356-
locktime: self.dual_funding_context.funding_tx_locktime,
8522+
locktime: self.dual_funding_context.funding_tx_locktime.to_consensus_u32(),
83578523
require_confirmed_inputs: None,
83588524
}
83598525
}
@@ -8374,13 +8540,22 @@ impl<SP: Deref> InboundV2Channel<SP> where SP::Target: SignerProvider {
83748540
pub fn new<ES: Deref, F: Deref, L: Deref>(
83758541
fee_estimator: &LowerBoundedFeeEstimator<F>, entropy_source: &ES, signer_provider: &SP,
83768542
counterparty_node_id: PublicKey, our_supported_features: &ChannelTypeFeatures,
8377-
their_features: &InitFeatures, msg: &msgs::OpenChannelV2, funding_satoshis: u64, user_id: u128,
8378-
config: &UserConfig, current_chain_height: u32, logger: &L,
8543+
their_features: &InitFeatures, msg: &msgs::OpenChannelV2,
8544+
funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>, user_id: u128, config: &UserConfig,
8545+
current_chain_height: u32, logger: &L,
83798546
) -> Result<InboundV2Channel<SP>, ChannelError>
83808547
where ES::Target: EntropySource,
83818548
F::Target: FeeEstimator,
83828549
L::Target: Logger,
83838550
{
8551+
let funding_satoshis = calculate_our_funding_satoshis(
8552+
false, &funding_inputs, &[], msg.funding_feerate_sat_per_1000_weight,
8553+
msg.common_fields.dust_limit_satoshis
8554+
).map_err(|_| ChannelError::Close(
8555+
(
8556+
"Failed to accept channel".to_string(),
8557+
ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) },
8558+
)))?;
83848559
let channel_value_satoshis = funding_satoshis.saturating_add(msg.common_fields.funding_satoshis);
83858560
let counterparty_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
83868561
channel_value_satoshis, msg.common_fields.dust_limit_satoshis);
@@ -8429,19 +8604,43 @@ impl<SP: Deref> InboundV2Channel<SP> where SP::Target: SignerProvider {
84298604
&context.get_counterparty_pubkeys().revocation_basepoint);
84308605
context.channel_id = channel_id;
84318606

8432-
let chan = Self {
8607+
let mut channel = Self {
84338608
context,
84348609
unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 },
84358610
dual_funding_context: DualFundingChannelContext {
84368611
our_funding_satoshis: funding_satoshis,
84378612
their_funding_satoshis: msg.common_fields.funding_satoshis,
8438-
funding_tx_locktime: msg.locktime,
8613+
funding_tx_locktime: LockTime::from_consensus(msg.locktime),
84398614
funding_feerate_sat_per_1000_weight: msg.funding_feerate_sat_per_1000_weight,
8615+
our_funding_inputs: funding_inputs,
84408616
},
84418617
interactive_tx_constructor: None,
84428618
};
84438619

8444-
Ok(chan)
8620+
match InteractiveTxConstructor::new(
8621+
InteractiveTxConstructorArgs {
8622+
entropy_source,
8623+
channel_id: channel.context.channel_id,
8624+
feerate_sat_per_kw: channel.dual_funding_context.funding_feerate_sat_per_1000_weight,
8625+
funding_tx_locktime: channel.dual_funding_context.funding_tx_locktime,
8626+
is_initiator: false,
8627+
inputs_to_contribute: channel.dual_funding_context.our_funding_inputs.clone(),
8628+
outputs_to_contribute: Vec::new(),
8629+
expected_remote_shared_funding_output: Some((channel.context().get_funding_redeemscript(), channel.context().channel_value_satoshis)),
8630+
}
8631+
) {
8632+
Ok(tx_constructor) => {
8633+
channel.set_interactive_tx_constructor(tx_constructor);
8634+
},
8635+
Err(_) => {
8636+
return Err(ChannelError::Close((
8637+
"V2 channel rejected due to sender error".into(),
8638+
ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) },
8639+
)))
8640+
}
8641+
}
8642+
8643+
Ok(channel)
84458644
}
84468645

84478646
/// Marks an inbound channel as accepted and generates a [`msgs::AcceptChannelV2`] message which

0 commit comments

Comments
 (0)