//! imply it needs to fail HTLCs/payments/channels it manages).
//!
-use bitcoin::blockdata::block::{Block, BlockHeader};
+use bitcoin::blockdata::block::BlockHeader;
use bitcoin::blockdata::transaction::Transaction;
use bitcoin::blockdata::constants::genesis_block;
use bitcoin::network::constants::Network;
}
enum OnionPayload {
- /// Contains a total_msat (which may differ from value if this is a Multi-Path Payment) and a
- /// payment_secret which prevents path-probing attacks and can associate different HTLCs which
- /// are part of the same payment.
- Invoice(msgs::FinalOnionHopData),
+ /// Indicates this incoming onion payload is for the purpose of paying an invoice.
+ Invoice {
+ /// This is only here for backwards-compatibility in serialization, in the future it can be
+ /// removed, breaking clients running 0.0.106 and earlier.
+ _legacy_hop_data: msgs::FinalOnionHopData,
+ },
/// Contains the payer-provided preimage.
Spontaneous(PaymentPreimage),
}
+/// HTLCs that are to us and can be failed/claimed by the user
struct ClaimableHTLC {
prev_hop: HTLCPreviousHopData,
cltv_expiry: u32,
+ /// The amount (in msats) of this MPP part
value: u64,
onion_payload: OnionPayload,
timer_ticks: u8,
+ /// The sum total of all MPP parts
+ total_msat: u64,
}
/// A payment identifier used to uniquely identify a payment to LDK.
/// Information on the fees and requirements that the counterparty requires when forwarding
/// payments to us through this channel.
pub forwarding_info: Option<CounterpartyForwardingInfo>,
+ /// The smallest value HTLC (in msat) the remote peer will accept, for this channel. This field
+ /// is only `None` before we have received either the `OpenChannel` or `AcceptChannel` message
+ /// from the remote peer, or for `ChannelCounterparty` objects serialized prior to LDK 0.0.107.
+ pub outbound_htlc_minimum_msat: Option<u64>,
+ /// The largest value HTLC (in msat) the remote peer currently will accept, for this channel.
+ pub outbound_htlc_maximum_msat: Option<u64>,
}
/// Details of a channel, as returned by ChannelManager::list_channels and ChannelManager::list_usable_channels
/// conflict-avoidance policy, exactly this amount is not likely to be spendable. However, we
/// should be able to spend nearly this amount.
pub outbound_capacity_msat: u64,
+ /// The available outbound capacity for sending a single HTLC to the remote peer. This is
+ /// similar to [`ChannelDetails::outbound_capacity_msat`] but it may be further restricted by
+ /// the current state and per-HTLC limit(s). This is intended for use when routing, allowing us
+ /// to use a limit as close as possible to the HTLC limit we can currently send.
+ ///
+ /// See also [`ChannelDetails::balance_msat`] and [`ChannelDetails::outbound_capacity_msat`].
+ pub next_outbound_htlc_limit_msat: u64,
/// The available inbound capacity for the remote peer to send HTLCs to us. This does not
/// include any pending HTLCs which are not yet fully resolved (and, thus, whose balance is not
/// available for inclusion in new inbound HTLCs).
pub is_usable: bool,
/// True if this channel is (or will be) publicly-announced.
pub is_public: bool,
+ /// The smallest value HTLC (in msat) we will accept, for this channel. This field
+ /// is only `None` for `ChannelDetails` objects serialized prior to LDK 0.0.107
+ pub inbound_htlc_minimum_msat: Option<u64>,
+ /// The largest value HTLC (in msat) we currently will accept, for this channel.
+ pub inbound_htlc_maximum_msat: Option<u64>,
}
impl ChannelDetails {
let channel_state = self.channel_state.lock().unwrap();
res.reserve(channel_state.by_id.len());
for (channel_id, channel) in channel_state.by_id.iter().filter(f) {
- let (inbound_capacity_msat, outbound_capacity_msat) = channel.get_inbound_outbound_available_balance_msat();
- let balance_msat = channel.get_balance_msat();
+ let balance = channel.get_available_balances();
let (to_remote_reserve_satoshis, to_self_reserve_satoshis) =
channel.get_holder_counterparty_selected_channel_reserve_satoshis();
res.push(ChannelDetails {
features: InitFeatures::empty(),
unspendable_punishment_reserve: to_remote_reserve_satoshis,
forwarding_info: channel.counterparty_forwarding_info(),
+ // Ensures that we have actually received the `htlc_minimum_msat` value
+ // from the counterparty through the `OpenChannel` or `AcceptChannel`
+ // message (as they are always the first message from the counterparty).
+ // Else `Channel::get_counterparty_htlc_minimum_msat` could return the
+ // default `0` value set by `Channel::new_outbound`.
+ outbound_htlc_minimum_msat: if channel.have_received_message() {
+ Some(channel.get_counterparty_htlc_minimum_msat()) } else { None },
+ outbound_htlc_maximum_msat: channel.get_counterparty_htlc_maximum_msat(),
},
funding_txo: channel.get_funding_txo(),
// Note that accept_channel (or open_channel) is always the first message, so
inbound_scid_alias: channel.latest_inbound_scid_alias(),
channel_value_satoshis: channel.get_value_satoshis(),
unspendable_punishment_reserve: to_self_reserve_satoshis,
- balance_msat,
- inbound_capacity_msat,
- outbound_capacity_msat,
+ balance_msat: balance.balance_msat,
+ inbound_capacity_msat: balance.inbound_capacity_msat,
+ outbound_capacity_msat: balance.outbound_capacity_msat,
+ next_outbound_htlc_limit_msat: balance.next_outbound_htlc_limit_msat,
user_channel_id: channel.get_user_id(),
confirmations_required: channel.minimum_depth(),
force_close_spend_delay: channel.get_counterparty_selected_contest_delay(),
is_funding_locked: channel.is_usable(),
is_usable: channel.is_live(),
is_public: channel.should_announce(),
+ inbound_htlc_minimum_msat: Some(channel.get_holder_htlc_minimum_msat()),
+ inbound_htlc_maximum_msat: channel.get_holder_htlc_maximum_msat()
});
}
}
HTLCForwardInfo::AddHTLC { prev_short_channel_id, prev_htlc_id, forward_info: PendingHTLCInfo {
routing, incoming_shared_secret, payment_hash, amt_to_forward, .. },
prev_funding_outpoint } => {
- let (cltv_expiry, onion_payload, phantom_shared_secret) = match routing {
- PendingHTLCRouting::Receive { payment_data, incoming_cltv_expiry, phantom_shared_secret } =>
- (incoming_cltv_expiry, OnionPayload::Invoice(payment_data), phantom_shared_secret),
+ let (cltv_expiry, onion_payload, payment_data, phantom_shared_secret) = match routing {
+ PendingHTLCRouting::Receive { payment_data, incoming_cltv_expiry, phantom_shared_secret } => {
+ let _legacy_hop_data = payment_data.clone();
+ (incoming_cltv_expiry, OnionPayload::Invoice { _legacy_hop_data }, Some(payment_data), phantom_shared_secret)
+ },
PendingHTLCRouting::ReceiveKeysend { payment_preimage, incoming_cltv_expiry } =>
- (incoming_cltv_expiry, OnionPayload::Spontaneous(payment_preimage), None),
+ (incoming_cltv_expiry, OnionPayload::Spontaneous(payment_preimage), None, None),
_ => {
panic!("short_channel_id == 0 should imply any pending_forward entries are of type Receive");
}
},
value: amt_to_forward,
timer_ticks: 0,
+ total_msat: if let Some(data) = &payment_data { data.total_msat } else { amt_to_forward },
cltv_expiry,
onion_payload,
};
}
macro_rules! check_total_value {
- ($payment_data_total_msat: expr, $payment_secret: expr, $payment_preimage: expr) => {{
+ ($payment_data: expr, $payment_preimage: expr) => {{
let mut payment_received_generated = false;
let htlcs = channel_state.claimable_htlcs.entry(payment_hash)
.or_insert(Vec::new());
for htlc in htlcs.iter() {
total_value += htlc.value;
match &htlc.onion_payload {
- OnionPayload::Invoice(htlc_payment_data) => {
- if htlc_payment_data.total_msat != $payment_data_total_msat {
+ OnionPayload::Invoice { .. } => {
+ if htlc.total_msat != $payment_data.total_msat {
log_trace!(self.logger, "Failing HTLCs with payment_hash {} as the HTLCs had inconsistent total values (eg {} and {})",
- log_bytes!(payment_hash.0), $payment_data_total_msat, htlc_payment_data.total_msat);
+ log_bytes!(payment_hash.0), $payment_data.total_msat, htlc.total_msat);
total_value = msgs::MAX_VALUE_MSAT;
}
if total_value >= msgs::MAX_VALUE_MSAT { break; }
_ => unreachable!(),
}
}
- if total_value >= msgs::MAX_VALUE_MSAT || total_value > $payment_data_total_msat {
+ if total_value >= msgs::MAX_VALUE_MSAT || total_value > $payment_data.total_msat {
log_trace!(self.logger, "Failing HTLCs with payment_hash {} as the total value {} ran over expected value {} (or HTLCs were inconsistent)",
- log_bytes!(payment_hash.0), total_value, $payment_data_total_msat);
+ log_bytes!(payment_hash.0), total_value, $payment_data.total_msat);
fail_htlc!(claimable_htlc);
- } else if total_value == $payment_data_total_msat {
+ } else if total_value == $payment_data.total_msat {
htlcs.push(claimable_htlc);
new_events.push(events::Event::PaymentReceived {
payment_hash,
purpose: events::PaymentPurpose::InvoicePayment {
payment_preimage: $payment_preimage,
- payment_secret: $payment_secret,
+ payment_secret: $payment_data.payment_secret,
},
amt: total_value,
});
match payment_secrets.entry(payment_hash) {
hash_map::Entry::Vacant(_) => {
match claimable_htlc.onion_payload {
- OnionPayload::Invoice(ref payment_data) => {
- let payment_preimage = match inbound_payment::verify(payment_hash, payment_data.clone(), self.highest_seen_timestamp.load(Ordering::Acquire) as u64, &self.inbound_payment_key, &self.logger) {
+ OnionPayload::Invoice { .. } => {
+ let payment_data = payment_data.unwrap();
+ let payment_preimage = match inbound_payment::verify(payment_hash, &payment_data, self.highest_seen_timestamp.load(Ordering::Acquire) as u64, &self.inbound_payment_key, &self.logger) {
Ok(payment_preimage) => payment_preimage,
Err(()) => {
fail_htlc!(claimable_htlc);
continue
}
};
- let payment_data_total_msat = payment_data.total_msat;
- let payment_secret = payment_data.payment_secret.clone();
- check_total_value!(payment_data_total_msat, payment_secret, payment_preimage);
+ check_total_value!(payment_data, payment_preimage);
},
OnionPayload::Spontaneous(preimage) => {
match channel_state.claimable_htlcs.entry(payment_hash) {
}
},
hash_map::Entry::Occupied(inbound_payment) => {
- let payment_data =
- if let OnionPayload::Invoice(ref data) = claimable_htlc.onion_payload {
- data.clone()
- } else {
- log_trace!(self.logger, "Failing new keysend HTLC with payment_hash {} because we already have an inbound payment with the same payment hash", log_bytes!(payment_hash.0));
- fail_htlc!(claimable_htlc);
- continue
- };
+ if payment_data.is_none() {
+ log_trace!(self.logger, "Failing new keysend HTLC with payment_hash {} because we already have an inbound payment with the same payment hash", log_bytes!(payment_hash.0));
+ fail_htlc!(claimable_htlc);
+ continue
+ };
+ let payment_data = payment_data.unwrap();
if inbound_payment.get().payment_secret != payment_data.payment_secret {
log_trace!(self.logger, "Failing new HTLC with payment_hash {} as it didn't match our expected payment secret.", log_bytes!(payment_hash.0));
fail_htlc!(claimable_htlc);
log_bytes!(payment_hash.0), payment_data.total_msat, inbound_payment.get().min_value_msat.unwrap());
fail_htlc!(claimable_htlc);
} else {
- let payment_received_generated = check_total_value!(payment_data.total_msat, payment_data.payment_secret, inbound_payment.get().payment_preimage);
+ let payment_received_generated = check_total_value!(payment_data, inbound_payment.get().payment_preimage);
if payment_received_generated {
inbound_payment.remove_entry();
}
debug_assert!(false);
return false;
}
- if let OnionPayload::Invoice(ref final_hop_data) = htlcs[0].onion_payload {
+ if let OnionPayload::Invoice { .. } = htlcs[0].onion_payload {
// Check if we've received all the parts we need for an MPP (the value of the parts adds to total_msat).
// In this case we're not going to handle any timeouts of the parts here.
- if final_hop_data.total_msat == htlcs.iter().fold(0, |total, htlc| total + htlc.value) {
+ if htlcs[0].total_msat == htlcs.iter().fold(0, |total, htlc| total + htlc.value) {
return true;
} else if htlcs.into_iter().any(|htlc| {
htlc.timer_ticks += 1;
} else { None };
let mut pending_events = self.pending_events.lock().unwrap();
+
+ let source_channel_id = Some(prev_outpoint.to_channel_id());
pending_events.push(events::Event::PaymentForwarded {
+ source_channel_id,
fee_earned_msat,
claim_from_onchain_tx: from_onchain,
});
F::Target: FeeEstimator,
L::Target: Logger,
{
- fn block_connected(&self, block: &Block, height: u32) {
+ fn filtered_block_connected(&self, header: &BlockHeader, txdata: &TransactionData, height: u32) {
{
let best_block = self.best_block.read().unwrap();
- assert_eq!(best_block.block_hash(), block.header.prev_blockhash,
+ assert_eq!(best_block.block_hash(), header.prev_blockhash,
"Blocks must be connected in chain-order - the connected header must build on the last connected header");
assert_eq!(best_block.height(), height - 1,
"Blocks must be connected in chain-order - the connected block height must be one greater than the previous height");
}
- let txdata: Vec<_> = block.txdata.iter().enumerate().collect();
- self.transactions_confirmed(&block.header, &txdata, height);
- self.best_block_updated(&block.header, height);
+ self.transactions_confirmed(header, txdata, height);
+ self.best_block_updated(header, height);
}
fn block_disconnected(&self, header: &BlockHeader, height: u32) {
(4, features, required),
(6, unspendable_punishment_reserve, required),
(8, forwarding_info, option),
+ (9, outbound_htlc_minimum_msat, option),
+ (11, outbound_htlc_maximum_msat, option),
});
impl_writeable_tlv_based!(ChannelDetails, {
(14, user_channel_id, required),
(16, balance_msat, required),
(18, outbound_capacity_msat, required),
+ // Note that by the time we get past the required read above, outbound_capacity_msat will be
+ // filled in, so we can safely unwrap it here.
+ (19, next_outbound_htlc_limit_msat, (default_value, outbound_capacity_msat.0.unwrap())),
(20, inbound_capacity_msat, required),
(22, confirmations_required, option),
(24, force_close_spend_delay, option),
(28, is_funding_locked, required),
(30, is_usable, required),
(32, is_public, required),
+ (33, inbound_htlc_minimum_msat, option),
+ (35, inbound_htlc_maximum_msat, option),
});
impl_writeable_tlv_based!(PhantomRouteHints, {
impl Writeable for ClaimableHTLC {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
let payment_data = match &self.onion_payload {
- OnionPayload::Invoice(data) => Some(data.clone()),
+ OnionPayload::Invoice { _legacy_hop_data } => Some(_legacy_hop_data),
_ => None,
};
let keysend_preimage = match self.onion_payload {
- OnionPayload::Invoice(_) => None,
+ OnionPayload::Invoice { .. } => None,
OnionPayload::Spontaneous(preimage) => Some(preimage.clone()),
};
- write_tlv_fields!
- (writer,
- {
- (0, self.prev_hop, required), (2, self.value, required),
- (4, payment_data, option), (6, self.cltv_expiry, required),
- (8, keysend_preimage, option),
- });
+ write_tlv_fields!(writer, {
+ (0, self.prev_hop, required),
+ (1, self.total_msat, required),
+ (2, self.value, required),
+ (4, payment_data, option),
+ (6, self.cltv_expiry, required),
+ (8, keysend_preimage, option),
+ });
Ok(())
}
}
let mut value = 0;
let mut payment_data: Option<msgs::FinalOnionHopData> = None;
let mut cltv_expiry = 0;
+ let mut total_msat = None;
let mut keysend_preimage: Option<PaymentPreimage> = None;
- read_tlv_fields!
- (reader,
- {
- (0, prev_hop, required), (2, value, required),
- (4, payment_data, option), (6, cltv_expiry, required),
- (8, keysend_preimage, option)
- });
+ read_tlv_fields!(reader, {
+ (0, prev_hop, required),
+ (1, total_msat, option),
+ (2, value, required),
+ (4, payment_data, option),
+ (6, cltv_expiry, required),
+ (8, keysend_preimage, option)
+ });
let onion_payload = match keysend_preimage {
Some(p) => {
if payment_data.is_some() {
return Err(DecodeError::InvalidValue)
}
+ if total_msat.is_none() {
+ total_msat = Some(value);
+ }
OnionPayload::Spontaneous(p)
},
None => {
if payment_data.is_none() {
return Err(DecodeError::InvalidValue)
}
- OnionPayload::Invoice(payment_data.unwrap())
+ if total_msat.is_none() {
+ total_msat = Some(payment_data.as_ref().unwrap().total_msat);
+ }
+ OnionPayload::Invoice { _legacy_hop_data: payment_data.unwrap() }
},
};
Ok(Self {
prev_hop: prev_hop.0.unwrap(),
timer_ticks: 0,
value,
+ total_msat: total_msat.unwrap(),
onion_payload,
cltv_expiry,
})
// payment verification fails as expected.
let mut bad_payment_hash = payment_hash.clone();
bad_payment_hash.0[0] += 1;
- match inbound_payment::verify(bad_payment_hash, payment_data.clone(), nodes[0].node.highest_seen_timestamp.load(Ordering::Acquire) as u64, &nodes[0].node.inbound_payment_key, &nodes[0].logger) {
+ match inbound_payment::verify(bad_payment_hash, &payment_data, nodes[0].node.highest_seen_timestamp.load(Ordering::Acquire) as u64, &nodes[0].node.inbound_payment_key, &nodes[0].logger) {
Ok(_) => panic!("Unexpected ok"),
Err(()) => {
nodes[0].logger.assert_log_contains("lightning::ln::inbound_payment".to_string(), "Failing HTLC with user-generated payment_hash".to_string(), 1);
}
// Check that using the original payment hash succeeds.
- assert!(inbound_payment::verify(payment_hash, payment_data, nodes[0].node.highest_seen_timestamp.load(Ordering::Acquire) as u64, &nodes[0].node.inbound_payment_key, &nodes[0].logger).is_ok());
+ assert!(inbound_payment::verify(payment_hash, &payment_data, nodes[0].node.highest_seen_timestamp.load(Ordering::Acquire) as u64, &nodes[0].node.inbound_payment_key, &nodes[0].logger).is_ok());
}
}