Skip to content

Commit d608783

Browse files
committed
Implement support for accepting V2 channels
1 parent c9a9b70 commit d608783

File tree

4 files changed

+819
-183
lines changed

4 files changed

+819
-183
lines changed

lightning/src/events/mod.rs

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

594+
/// Used to indicate the kind of funding for this channel by the channel acceptor (us).
595+
///
596+
/// Allows the differentiation between a request for a dual-funded and non-dual-funded channel.
597+
#[derive(Clone, Debug, PartialEq, Eq)]
598+
pub enum InboundChannelFunds {
599+
/// For a non-dual-funded channel, the `push_msat` value from the channel initiator to us.
600+
PushMsat(u64),
601+
/// Indicates the open request is for a dual funded channel.
602+
///
603+
/// Note that these channels do not support starting with initial funds pushed from the counterparty,
604+
/// who is the channel opener in this case.
605+
DualFunded,
606+
}
607+
594608
/// An Event which you should probably take some action in response to.
595609
///
596610
/// Note that while Writeable and Readable are implemented for Event, you probably shouldn't use
@@ -1322,9 +1336,14 @@ pub enum Event {
13221336
},
13231337
/// Indicates a request to open a new channel by a peer.
13241338
///
1325-
/// To accept the request, call [`ChannelManager::accept_inbound_channel`]. To reject the request,
1326-
/// call [`ChannelManager::force_close_without_broadcasting_txn`]. Note that a ['ChannelClosed`]
1327-
/// event will _not_ be triggered if the channel is rejected.
1339+
/// If `channel_negotiation_type` is `InboundChannelFunds::DualFunded`, this indicates that the peer wishes to
1340+
/// open a dual-funded channel. Otherwise, this field will be `InboundChannelFunds::PushMsats`,
1341+
/// indicating the `push_msats` value our peer is pushing to us for a non-dual-funded channel.
1342+
///
1343+
/// To accept the request (and in the case of a dual-funded channel, not contribute funds),
1344+
/// call [`ChannelManager::accept_inbound_channel`].
1345+
/// To reject the request, call [`ChannelManager::force_close_without_broadcasting_txn`].
1346+
/// Note that a ['ChannelClosed`] event will _not_ be triggered if the channel is rejected.
13281347
///
13291348
/// The event is only triggered when a new open channel request is received and the
13301349
/// [`UserConfig::manually_accept_inbound_channels`] config flag is set to true.
@@ -1358,8 +1377,10 @@ pub enum Event {
13581377
counterparty_node_id: PublicKey,
13591378
/// The channel value of the requested channel.
13601379
funding_satoshis: u64,
1361-
/// Our starting balance in the channel if the request is accepted, in milli-satoshi.
1362-
push_msat: u64,
1380+
/// If `channel_negotiation_type` is `InboundChannelFunds::DualFunded`, this indicates that the peer wishes to
1381+
/// open a dual-funded channel. Otherwise, this field will be `InboundChannelFunds::PushMsats`,
1382+
/// indicating the `push_msats` value our peer is pushing to us for a non-dual-funded channel.
1383+
channel_negotiation_type: InboundChannelFunds,
13631384
/// The features that this channel will operate with. If you reject the channel, a
13641385
/// well-behaved counterparty may automatically re-attempt the channel with a new set of
13651386
/// 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;
@@ -28,7 +29,11 @@ use bitcoin::secp256k1;
2829
use crate::ln::types::ChannelId;
2930
use crate::types::payment::{PaymentPreimage, PaymentHash};
3031
use crate::types::features::{ChannelTypeFeatures, InitFeatures};
31-
use crate::ln::interactivetxs::InteractiveTxConstructor;
32+
use crate::ln::interactivetxs::{
33+
estimate_input_weight, get_output_weight, HandleTxCompleteResult,
34+
InteractiveTxConstructor, InteractiveTxConstructorArgs, InteractiveTxMessageSend,
35+
InteractiveTxMessageSendResult, TX_COMMON_FIELDS_WEIGHT,
36+
};
3237
use crate::ln::msgs;
3338
use crate::ln::msgs::{ClosingSigned, ClosingSignedFeeRange, DecodeError};
3439
use crate::ln::script::{self, ShutdownScript};
@@ -45,14 +50,14 @@ use crate::ln::chan_utils::{
4550
use crate::ln::chan_utils;
4651
use crate::ln::onion_utils::HTLCFailReason;
4752
use crate::chain::BestBlock;
48-
use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, LowerBoundedFeeEstimator};
53+
use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, LowerBoundedFeeEstimator, fee_for_weight};
4954
use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, ChannelMonitorUpdateStep, LATENCY_GRACE_PERIOD_BLOCKS, CLOSED_CHANNEL_UPDATE_ID};
5055
use crate::chain::transaction::{OutPoint, TransactionData};
5156
use crate::sign::ecdsa::EcdsaChannelSigner;
5257
use crate::sign::{EntropySource, ChannelSigner, SignerProvider, NodeSigner, Recipient};
5358
use crate::events::ClosureReason;
5459
use crate::routing::gossip::NodeId;
55-
use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer};
60+
use crate::util::ser::{Readable, ReadableArgs, TransactionU16LenLimited, Writeable, Writer};
5661
use crate::util::logger::{Logger, Record, WithContext};
5762
use crate::util::errors::APIError;
5863
use crate::util::config::{UserConfig, ChannelConfig, LegacyChannelConfig, ChannelHandshakeConfig, ChannelHandshakeLimits, MaxDustHTLCExposure};
@@ -1637,6 +1642,109 @@ impl<SP: Deref> InitialRemoteCommitmentReceiver<SP> for InboundV1Channel<SP> whe
16371642
}
16381643
}
16391644

1645+
pub(super) trait InteractivelyFunded<SP: Deref> where SP::Target: SignerProvider {
1646+
fn context(&self) -> &ChannelContext<SP>;
1647+
1648+
fn context_mut(&mut self) -> &mut ChannelContext<SP>;
1649+
1650+
fn interactive_tx_constructor_mut(&mut self) -> &mut Option<InteractiveTxConstructor>;
1651+
1652+
fn dual_funding_context(&self) -> &DualFundingChannelContext;
1653+
1654+
fn set_interactive_tx_constructor(&mut self, interactive_tx_constructor: InteractiveTxConstructor);
1655+
1656+
fn tx_add_input(&mut self, msg: &msgs::TxAddInput) -> InteractiveTxMessageSendResult {
1657+
InteractiveTxMessageSendResult(match self.interactive_tx_constructor_mut() {
1658+
Some(ref mut tx_constructor) => tx_constructor.handle_tx_add_input(msg).map_err(
1659+
|reason| reason.into_tx_abort_msg(self.context().channel_id())),
1660+
None => Err(msgs::TxAbort {
1661+
channel_id: self.context().channel_id(),
1662+
data: b"No interactive transaction negotiation in progress".to_vec()
1663+
}),
1664+
})
1665+
}
1666+
1667+
fn tx_add_output(&mut self, msg: &msgs::TxAddOutput)-> InteractiveTxMessageSendResult {
1668+
InteractiveTxMessageSendResult(match self.interactive_tx_constructor_mut() {
1669+
Some(ref mut tx_constructor) => tx_constructor.handle_tx_add_output(msg).map_err(
1670+
|reason| reason.into_tx_abort_msg(self.context().channel_id())),
1671+
None => Err(msgs::TxAbort {
1672+
channel_id: self.context().channel_id(),
1673+
data: b"No interactive transaction negotiation in progress".to_vec()
1674+
}),
1675+
})
1676+
}
1677+
1678+
fn tx_remove_input(&mut self, msg: &msgs::TxRemoveInput)-> InteractiveTxMessageSendResult {
1679+
InteractiveTxMessageSendResult(match self.interactive_tx_constructor_mut() {
1680+
Some(ref mut tx_constructor) => tx_constructor.handle_tx_remove_input(msg).map_err(
1681+
|reason| reason.into_tx_abort_msg(self.context().channel_id())),
1682+
None => Err(msgs::TxAbort {
1683+
channel_id: self.context().channel_id(),
1684+
data: b"No interactive transaction negotiation in progress".to_vec()
1685+
}),
1686+
})
1687+
}
1688+
1689+
fn tx_remove_output(&mut self, msg: &msgs::TxRemoveOutput)-> InteractiveTxMessageSendResult {
1690+
InteractiveTxMessageSendResult(match self.interactive_tx_constructor_mut() {
1691+
Some(ref mut tx_constructor) => tx_constructor.handle_tx_remove_output(msg).map_err(
1692+
|reason| reason.into_tx_abort_msg(self.context().channel_id())),
1693+
None => Err(msgs::TxAbort {
1694+
channel_id: self.context().channel_id(),
1695+
data: b"No interactive transaction negotiation in progress".to_vec()
1696+
}),
1697+
})
1698+
}
1699+
1700+
fn tx_complete(&mut self, msg: &msgs::TxComplete) -> HandleTxCompleteResult {
1701+
HandleTxCompleteResult(match self.interactive_tx_constructor_mut() {
1702+
Some(ref mut tx_constructor) => tx_constructor.handle_tx_complete(msg).map_err(
1703+
|reason| reason.into_tx_abort_msg(self.context().channel_id())),
1704+
None => Err(msgs::TxAbort {
1705+
channel_id: self.context().channel_id(),
1706+
data: b"No interactive transaction negotiation in progress".to_vec()
1707+
}),
1708+
})
1709+
}
1710+
}
1711+
1712+
impl<SP: Deref> InteractivelyFunded<SP> for OutboundV2Channel<SP> where SP::Target: SignerProvider {
1713+
fn context(&self) -> &ChannelContext<SP> {
1714+
&self.context
1715+
}
1716+
fn context_mut(&mut self) -> &mut ChannelContext<SP> {
1717+
&mut self.context
1718+
}
1719+
fn dual_funding_context(&self) -> &DualFundingChannelContext {
1720+
&self.dual_funding_context
1721+
}
1722+
fn interactive_tx_constructor_mut(&mut self) -> &mut Option<InteractiveTxConstructor> {
1723+
&mut self.interactive_tx_constructor
1724+
}
1725+
fn set_interactive_tx_constructor(&mut self, interactive_tx_constructor: InteractiveTxConstructor) {
1726+
self.interactive_tx_constructor = Some(interactive_tx_constructor);
1727+
}
1728+
}
1729+
1730+
impl<SP: Deref> InteractivelyFunded<SP> for InboundV2Channel<SP> where SP::Target: SignerProvider {
1731+
fn context(&self) -> &ChannelContext<SP> {
1732+
&self.context
1733+
}
1734+
fn context_mut(&mut self) -> &mut ChannelContext<SP> {
1735+
&mut self.context
1736+
}
1737+
fn dual_funding_context(&self) -> &DualFundingChannelContext {
1738+
&self.dual_funding_context
1739+
}
1740+
fn interactive_tx_constructor_mut(&mut self) -> &mut Option<InteractiveTxConstructor> {
1741+
&mut self.interactive_tx_constructor
1742+
}
1743+
fn set_interactive_tx_constructor(&mut self, interactive_tx_constructor: InteractiveTxConstructor) {
1744+
self.interactive_tx_constructor = Some(interactive_tx_constructor);
1745+
}
1746+
}
1747+
16401748
impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
16411749
fn new_for_inbound_channel<'a, ES: Deref, F: Deref, L: Deref>(
16421750
fee_estimator: &'a LowerBoundedFeeEstimator<F>,
@@ -3766,6 +3874,16 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
37663874
self.channel_transaction_parameters.channel_type_features = self.channel_type.clone();
37673875
Ok(())
37683876
}
3877+
3878+
// Interactive transaction construction
3879+
3880+
pub fn tx_signatures(&self, msg: &msgs::TxSignatures) -> Result<InteractiveTxMessageSend, ChannelError> {
3881+
todo!();
3882+
}
3883+
3884+
pub fn tx_abort(&self, msg: &msgs::TxAbort) -> Result<InteractiveTxMessageSend, ChannelError> {
3885+
todo!();
3886+
}
37693887
}
37703888

37713889
// Internal utility functions for channels
@@ -3823,6 +3941,42 @@ fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satos
38233941
cmp::min(channel_value_satoshis, cmp::max(q, dust_limit_satoshis))
38243942
}
38253943

3944+
pub(super) fn calculate_our_funding_satoshis(
3945+
is_initiator: bool, funding_inputs: &[(TxIn, TransactionU16LenLimited)],
3946+
funding_outputs: &[TxOut], funding_feerate_sat_per_1000_weight: u32,
3947+
holder_dust_limit_satoshis: u64,
3948+
) -> Result<u64, APIError> {
3949+
let mut total_input_satoshis = 0u64;
3950+
let mut our_contributed_weight = 0u64;
3951+
3952+
for (idx, input) in funding_inputs.iter().enumerate() {
3953+
if let Some(output) = input.1.as_transaction().output.get(input.0.previous_output.vout as usize) {
3954+
total_input_satoshis = total_input_satoshis.saturating_add(output.value.to_sat());
3955+
our_contributed_weight = our_contributed_weight.saturating_add(estimate_input_weight(output).to_wu());
3956+
} else {
3957+
return Err(APIError::APIMisuseError {
3958+
err: format!("Transaction with txid {} does not have an output with vout of {} corresponding to TxIn at funding_inputs[{}]",
3959+
input.1.as_transaction().compute_txid(), input.0.previous_output.vout, idx) });
3960+
}
3961+
}
3962+
our_contributed_weight = our_contributed_weight.saturating_add(funding_outputs.iter().fold(0u64, |weight, txout| {
3963+
weight.saturating_add(get_output_weight(&txout.script_pubkey).to_wu())
3964+
}));
3965+
3966+
// If we are the initiator, we must pay for weight of all common fields in the funding transaction.
3967+
if is_initiator {
3968+
our_contributed_weight = our_contributed_weight.saturating_add(TX_COMMON_FIELDS_WEIGHT);
3969+
}
3970+
3971+
let funding_satoshis = total_input_satoshis
3972+
.saturating_sub(fee_for_weight(funding_feerate_sat_per_1000_weight, our_contributed_weight));
3973+
if funding_satoshis < holder_dust_limit_satoshis {
3974+
Ok(0)
3975+
} else {
3976+
Ok(funding_satoshis)
3977+
}
3978+
}
3979+
38263980
/// Context for dual-funded channels.
38273981
pub(super) struct DualFundingChannelContext {
38283982
/// The amount in satoshis we will be contributing to the channel.
@@ -3831,9 +3985,15 @@ pub(super) struct DualFundingChannelContext {
38313985
pub their_funding_satoshis: u64,
38323986
/// The funding transaction locktime suggested by the initiator. If set by us, it is always set
38333987
/// to the current block height to align incentives against fee-sniping.
3834-
pub funding_tx_locktime: u32,
3988+
pub funding_tx_locktime: LockTime,
38353989
/// The feerate set by the initiator to be used for the funding transaction.
38363990
pub funding_feerate_sat_per_1000_weight: u32,
3991+
/// The funding inputs we will be contributing to the channel.
3992+
///
3993+
/// Note that the `our_funding_satoshis` field is equal to the total value of `our_funding_inputs`
3994+
/// minus any fees paid for our contributed weight. This means that change will never be generated
3995+
/// and the maximum value possible will go towards funding the channel.
3996+
pub our_funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>,
38373997
}
38383998

38393999
// Holder designates channel data owned for the benefit of the user client.
@@ -8264,8 +8424,9 @@ impl<SP: Deref> OutboundV2Channel<SP> where SP::Target: SignerProvider {
82648424
pub fn new<ES: Deref, F: Deref, L: Deref>(
82658425
fee_estimator: &LowerBoundedFeeEstimator<F>, entropy_source: &ES, signer_provider: &SP,
82668426
counterparty_node_id: PublicKey, their_features: &InitFeatures, funding_satoshis: u64,
8267-
user_id: u128, config: &UserConfig, current_chain_height: u32, outbound_scid_alias: u64,
8268-
funding_confirmation_target: ConfirmationTarget, logger: L,
8427+
funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>, user_id: u128, config: &UserConfig,
8428+
current_chain_height: u32, outbound_scid_alias: u64, funding_confirmation_target: ConfirmationTarget,
8429+
logger: L,
82698430
) -> Result<OutboundV2Channel<SP>, APIError>
82708431
where ES::Target: EntropySource,
82718432
F::Target: FeeEstimator,
@@ -8281,7 +8442,11 @@ impl<SP: Deref> OutboundV2Channel<SP> where SP::Target: SignerProvider {
82818442
funding_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS);
82828443

82838444
let funding_feerate_sat_per_1000_weight = fee_estimator.bounded_sat_per_1000_weight(funding_confirmation_target);
8284-
let funding_tx_locktime = current_chain_height;
8445+
let funding_tx_locktime = LockTime::from_height(current_chain_height)
8446+
.map_err(|_| APIError::APIMisuseError {
8447+
err: format!(
8448+
"Provided current chain height of {} doesn't make sense for a height-based timelock for the funding transaction",
8449+
current_chain_height) })?;
82858450

82868451
let chan = Self {
82878452
context: ChannelContext::new_for_outbound_channel(
@@ -8309,6 +8474,7 @@ impl<SP: Deref> OutboundV2Channel<SP> where SP::Target: SignerProvider {
83098474
their_funding_satoshis: 0,
83108475
funding_tx_locktime,
83118476
funding_feerate_sat_per_1000_weight,
8477+
our_funding_inputs: funding_inputs,
83128478
},
83138479
interactive_tx_constructor: None,
83148480
};
@@ -8373,7 +8539,7 @@ impl<SP: Deref> OutboundV2Channel<SP> where SP::Target: SignerProvider {
83738539
},
83748540
funding_feerate_sat_per_1000_weight: self.context.feerate_per_kw,
83758541
second_per_commitment_point,
8376-
locktime: self.dual_funding_context.funding_tx_locktime,
8542+
locktime: self.dual_funding_context.funding_tx_locktime.to_consensus_u32(),
83778543
require_confirmed_inputs: None,
83788544
}
83798545
}
@@ -8394,13 +8560,22 @@ impl<SP: Deref> InboundV2Channel<SP> where SP::Target: SignerProvider {
83948560
pub fn new<ES: Deref, F: Deref, L: Deref>(
83958561
fee_estimator: &LowerBoundedFeeEstimator<F>, entropy_source: &ES, signer_provider: &SP,
83968562
counterparty_node_id: PublicKey, our_supported_features: &ChannelTypeFeatures,
8397-
their_features: &InitFeatures, msg: &msgs::OpenChannelV2, funding_satoshis: u64, user_id: u128,
8398-
config: &UserConfig, current_chain_height: u32, logger: &L,
8563+
their_features: &InitFeatures, msg: &msgs::OpenChannelV2,
8564+
funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>, user_id: u128, config: &UserConfig,
8565+
current_chain_height: u32, logger: &L,
83998566
) -> Result<InboundV2Channel<SP>, ChannelError>
84008567
where ES::Target: EntropySource,
84018568
F::Target: FeeEstimator,
84028569
L::Target: Logger,
84038570
{
8571+
let funding_satoshis = calculate_our_funding_satoshis(
8572+
false, &funding_inputs, &[], msg.funding_feerate_sat_per_1000_weight,
8573+
msg.common_fields.dust_limit_satoshis
8574+
).map_err(|_| ChannelError::Close(
8575+
(
8576+
"Failed to accept channel".to_string(),
8577+
ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) },
8578+
)))?;
84048579
let channel_value_satoshis = funding_satoshis.saturating_add(msg.common_fields.funding_satoshis);
84058580
let counterparty_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
84068581
channel_value_satoshis, msg.common_fields.dust_limit_satoshis);
@@ -8449,19 +8624,43 @@ impl<SP: Deref> InboundV2Channel<SP> where SP::Target: SignerProvider {
84498624
&context.get_counterparty_pubkeys().revocation_basepoint);
84508625
context.channel_id = channel_id;
84518626

8452-
let chan = Self {
8627+
let mut channel = Self {
84538628
context,
84548629
unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 },
84558630
dual_funding_context: DualFundingChannelContext {
84568631
our_funding_satoshis: funding_satoshis,
84578632
their_funding_satoshis: msg.common_fields.funding_satoshis,
8458-
funding_tx_locktime: msg.locktime,
8633+
funding_tx_locktime: LockTime::from_consensus(msg.locktime),
84598634
funding_feerate_sat_per_1000_weight: msg.funding_feerate_sat_per_1000_weight,
8635+
our_funding_inputs: funding_inputs,
84608636
},
84618637
interactive_tx_constructor: None,
84628638
};
84638639

8464-
Ok(chan)
8640+
match InteractiveTxConstructor::new(
8641+
InteractiveTxConstructorArgs {
8642+
entropy_source,
8643+
channel_id: channel.context.channel_id,
8644+
feerate_sat_per_kw: channel.dual_funding_context.funding_feerate_sat_per_1000_weight,
8645+
funding_tx_locktime: channel.dual_funding_context.funding_tx_locktime,
8646+
is_initiator: false,
8647+
inputs_to_contribute: channel.dual_funding_context.our_funding_inputs.clone(),
8648+
outputs_to_contribute: Vec::new(),
8649+
expected_remote_shared_funding_output: Some((channel.context().get_funding_redeemscript(), channel.context().channel_value_satoshis)),
8650+
}
8651+
) {
8652+
Ok(tx_constructor) => {
8653+
channel.set_interactive_tx_constructor(tx_constructor);
8654+
},
8655+
Err(_) => {
8656+
return Err(ChannelError::Close((
8657+
"V2 channel rejected due to sender error".into(),
8658+
ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) },
8659+
)))
8660+
}
8661+
}
8662+
8663+
Ok(channel)
84658664
}
84668665

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

0 commit comments

Comments
 (0)