Skip to content

Commit

Permalink
Expose skimmed_fee_msat in PaymentForwarded
Browse files Browse the repository at this point in the history
We generally allow routing nodes to forward less than the expected HTLC
amount, if the receiver knowingly accepts this and claims the
underpaying HTLC (see `ChannelConfig::accept_underpaying_htlcs`). This
use case is in particular useful for the LSPS2/JIT channel setting where
the intial underpaying HTLC pays for the channel open.

While we previously exposed the withheld amount as
`PaymentClaimable::counterparty_skimmed_fee_msat` on the receiver side,
we did not individually provide it on the forwarding node's side.
Here, we therefore expose this additionally withheld amount via
`PaymentForwarded::skimmed_fee_msat`.
  • Loading branch information
tnull committed Jan 29, 2024
1 parent 51d9ee3 commit d0085fd
Show file tree
Hide file tree
Showing 10 changed files with 74 additions and 39 deletions.
21 changes: 18 additions & 3 deletions lightning/src/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -783,7 +783,7 @@ pub enum Event {
/// The outgoing channel between the next node and us. This is only `None` for events
/// generated or serialized by versions prior to 0.0.107.
next_channel_id: Option<ChannelId>,
/// The fee, in milli-satoshis, which was earned as a result of the payment.
/// The total fee, in milli-satoshis, which was earned as a result of the payment.
///
/// Note that if we force-closed the channel over which we forwarded an HTLC while the HTLC
/// was pending, the amount the next hop claimed will have been rounded down to the nearest
Expand All @@ -797,6 +797,18 @@ pub enum Event {
/// `PaymentForwarded` events are generated for the same payment iff `fee_earned_msat` is
/// `None`.
fee_earned_msat: Option<u64>,
/// The share of the total fee, in milli-satoshis, which was withheld in addition to the
/// forwarding fee.
///
/// This will only be `Some` if we forwarded an intercepted HTLC with less than the
/// expected amount. This means our counterparty accepted to receive less than the invoice
/// amount, e.g., by claiming the payment featuring a corresponding
/// [`PaymentClaimable::counterparty_skimmed_fee_msat`]
///
/// The caveat described above the `fee_earned_msat` field applies here as well.
///
/// [`PaymentClaimable::counterparty_skimmed_fee_msat`]: Self::PaymentClaimable::counterparty_skimmed_fee_msat
skimmed_fee_msat: Option<u64>,
/// If this is `true`, the forwarded HTLC was claimed by our counterparty via an on-chain
/// transaction.
claim_from_onchain_tx: bool,
Expand Down Expand Up @@ -1084,7 +1096,7 @@ impl Writeable for Event {
}
&Event::PaymentForwarded {
fee_earned_msat, prev_channel_id, claim_from_onchain_tx,
next_channel_id, outbound_amount_forwarded_msat
next_channel_id, outbound_amount_forwarded_msat, skimmed_fee_msat,
} => {
7u8.write(writer)?;
write_tlv_fields!(writer, {
Expand All @@ -1093,6 +1105,7 @@ impl Writeable for Event {
(2, claim_from_onchain_tx, required),
(3, next_channel_id, option),
(5, outbound_amount_forwarded_msat, option),
(7, skimmed_fee_msat, option),
});
},
&Event::ChannelClosed { ref channel_id, ref user_channel_id, ref reason,
Expand Down Expand Up @@ -1389,16 +1402,18 @@ impl MaybeReadable for Event {
let mut claim_from_onchain_tx = false;
let mut next_channel_id = None;
let mut outbound_amount_forwarded_msat = None;
let mut skimmed_fee_msat = None;
read_tlv_fields!(reader, {
(0, fee_earned_msat, option),
(1, prev_channel_id, option),
(2, claim_from_onchain_tx, required),
(3, next_channel_id, option),
(5, outbound_amount_forwarded_msat, option),
(7, skimmed_fee_msat, option),
});
Ok(Some(Event::PaymentForwarded {
fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id,
outbound_amount_forwarded_msat
outbound_amount_forwarded_msat, skimmed_fee_msat,
}))
};
f()
Expand Down
16 changes: 8 additions & 8 deletions lightning/src/ln/chanmon_update_fail_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1107,7 +1107,7 @@ fn test_monitor_update_fail_reestablish() {
assert!(updates.update_fee.is_none());
assert_eq!(updates.update_fulfill_htlcs.len(), 1);
nodes[1].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &updates.update_fulfill_htlcs[0]);
expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], Some(1000), false, false);
expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], Some(1000), None, false, false);
check_added_monitors!(nodes[1], 1);
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
commitment_signed_dance!(nodes[1], nodes[2], updates.commitment_signed, false);
Expand Down Expand Up @@ -2153,7 +2153,7 @@ fn test_fail_htlc_on_broadcast_after_claim() {
nodes[1].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &cs_updates.update_fulfill_htlcs[0]);
let bs_updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
check_added_monitors!(nodes[1], 1);
expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], Some(1000), false, false);
expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], Some(1000), None, false, false);

mine_transaction(&nodes[1], &bs_txn[0]);
check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[2].node.get_our_node_id()], 100000);
Expand Down Expand Up @@ -2526,7 +2526,7 @@ fn do_test_reconnect_dup_htlc_claims(htlc_status: HTLCStatusAtDupClaim, second_f
assert_eq!(fulfill_msg, cs_updates.update_fulfill_htlcs[0]);
}
nodes[1].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &fulfill_msg);
expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], Some(1000), false, false);
expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], Some(1000), None, false, false);
check_added_monitors!(nodes[1], 1);

let mut bs_updates = None;
Expand Down Expand Up @@ -3156,7 +3156,7 @@ fn do_test_inverted_mon_completion_order(with_latest_manager: bool, complete_bc_
nodes[0].node.handle_update_fulfill_htlc(&nodes[1].node.get_our_node_id(), &bs_updates.update_fulfill_htlcs[0]);
do_commitment_signed_dance(&nodes[0], &nodes[1], &bs_updates.commitment_signed, false, false);

expect_payment_forwarded!(nodes[1], &nodes[0], &nodes[2], Some(1_000), false, !with_latest_manager);
expect_payment_forwarded!(nodes[1], &nodes[0], &nodes[2], Some(1_000), None, false, !with_latest_manager);

// Finally, check that the payment was, ultimately, seen as sent by node A.
expect_payment_sent(&nodes[0], payment_preimage, None, true, true);
Expand Down Expand Up @@ -3287,7 +3287,7 @@ fn do_test_durable_preimages_on_closed_channel(close_chans_before_reload: bool,
} else {
// While we forwarded the payment a while ago, we don't want to process events too early or
// we'll run background tasks we wanted to test individually.
expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], None, true, !close_only_a);
expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], None, None, true, !close_only_a);
}

mine_transactions(&nodes[0], &[&as_closing_tx[0], bs_preimage_tx]);
Expand All @@ -3310,7 +3310,7 @@ fn do_test_durable_preimages_on_closed_channel(close_chans_before_reload: bool,
reconnect_nodes(reconnect_args);
let (outpoint, ab_update_id, _) = nodes[1].chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id_ab).unwrap().clone();
nodes[1].chain_monitor.chain_monitor.force_channel_monitor_updated(outpoint, ab_update_id);
expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], Some(1000), true, false);
expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], Some(1000), None, true, false);
if !close_chans_before_reload {
// Once we call `process_pending_events` the final `ChannelMonitor` for the B<->C
// channel will fly, removing the payment preimage from it.
Expand Down Expand Up @@ -3404,7 +3404,7 @@ fn do_test_reload_mon_update_completion_actions(close_during_reload: bool) {
let bc_update_id = nodes[1].chain_monitor.latest_monitor_update_id.lock().unwrap().get(&chan_id_bc).unwrap().2;
let mut events = nodes[1].node.get_and_clear_pending_events();
assert_eq!(events.len(), if close_during_reload { 2 } else { 1 });
expect_payment_forwarded(events.pop().unwrap(), &nodes[1], &nodes[0], &nodes[2], Some(1000), close_during_reload, false);
expect_payment_forwarded(events.pop().unwrap(), &nodes[1], &nodes[0], &nodes[2], Some(1000), None, close_during_reload, false);
if close_during_reload {
match events[0] {
Event::ChannelClosed { .. } => {},
Expand Down Expand Up @@ -3478,7 +3478,7 @@ fn do_test_glacial_peer_cant_hang(hold_chan_a: bool) {
reconnect_nodes(reconnect);

if !hold_chan_a {
expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], Some(1000), false, false);
expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], Some(1000), None, false, false);
send_payment(&nodes[0], &[&nodes[1], &nodes[2]], 100_000);
} else {
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
Expand Down
4 changes: 2 additions & 2 deletions lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3304,15 +3304,15 @@ impl<SP: Deref> Channel<SP> where
Err(ChannelError::Close("Remote tried to fulfill/fail an HTLC we couldn't find".to_owned()))
}

pub fn update_fulfill_htlc(&mut self, msg: &msgs::UpdateFulfillHTLC) -> Result<(HTLCSource, u64), ChannelError> {
pub fn update_fulfill_htlc(&mut self, msg: &msgs::UpdateFulfillHTLC) -> Result<(HTLCSource, u64, Option<u64>), ChannelError> {
if !matches!(self.context.channel_state, ChannelState::ChannelReady(_)) {
return Err(ChannelError::Close("Got fulfill HTLC message when channel was not in an operational state".to_owned()));
}
if self.context.channel_state.is_peer_disconnected() {
return Err(ChannelError::Close("Peer sent update_fulfill_htlc when we needed a channel_reestablish".to_owned()));
}

self.mark_outbound_htlc_removed(msg.htlc_id, Some(msg.payment_preimage), None).map(|htlc| (htlc.source.clone(), htlc.amount_msat))
self.mark_outbound_htlc_removed(msg.htlc_id, Some(msg.payment_preimage), None).map(|htlc| (htlc.source.clone(), htlc.amount_msat, htlc.skimmed_fee_msat))
}

pub fn update_fail_htlc(&mut self, msg: &msgs::UpdateFailHTLC, fail_reason: HTLCFailReason) -> Result<(), ChannelError> {
Expand Down
20 changes: 14 additions & 6 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5668,8 +5668,9 @@ where
}

fn claim_funds_internal(&self, source: HTLCSource, payment_preimage: PaymentPreimage,
forwarded_htlc_value_msat: Option<u64>, from_onchain: bool, startup_replay: bool,
next_channel_counterparty_node_id: Option<PublicKey>, next_channel_outpoint: OutPoint
forwarded_htlc_value_msat: Option<u64>, skimmed_fee_msat: Option<u64>, from_onchain: bool,
startup_replay: bool, next_channel_counterparty_node_id: Option<PublicKey>,
next_channel_outpoint: OutPoint
) {
match source {
HTLCSource::OutboundRoute { session_priv, payment_id, path, .. } => {
Expand Down Expand Up @@ -5767,13 +5768,16 @@ where
Some(claimed_htlc_value - forwarded_htlc_value)
} else { None }
} else { None };
debug_assert!(skimmed_fee_msat <= fee_earned_msat,
"skimmed_fee_msat must always be included in fee_earned_msat");
Some(MonitorUpdateCompletionAction::EmitEventAndFreeOtherChannel {
event: events::Event::PaymentForwarded {
fee_earned_msat,
claim_from_onchain_tx: from_onchain,
prev_channel_id: Some(prev_outpoint.to_channel_id()),
next_channel_id: Some(next_channel_outpoint.to_channel_id()),
outbound_amount_forwarded_msat: forwarded_htlc_value_msat,
skimmed_fee_msat,
},
downstream_counterparty_and_funding_outpoint: chan_to_release,
})
Expand Down Expand Up @@ -6712,7 +6716,7 @@ where

fn internal_update_fulfill_htlc(&self, counterparty_node_id: &PublicKey, msg: &msgs::UpdateFulfillHTLC) -> Result<(), MsgHandleErrInternal> {
let funding_txo;
let (htlc_source, forwarded_htlc_value) = {
let (htlc_source, forwarded_htlc_value, skimmed_fee_msat) = {
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
.ok_or_else(|| {
Expand Down Expand Up @@ -6750,7 +6754,11 @@ where
hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id))
}
};
self.claim_funds_internal(htlc_source, msg.payment_preimage.clone(), Some(forwarded_htlc_value), false, false, Some(*counterparty_node_id), funding_txo);
self.claim_funds_internal(htlc_source, msg.payment_preimage.clone(),
Some(forwarded_htlc_value), skimmed_fee_msat, false, false, Some(*counterparty_node_id),
funding_txo
);

Ok(())
}

Expand Down Expand Up @@ -7246,7 +7254,7 @@ where
let logger = WithContext::from(&self.logger, counterparty_node_id, Some(funding_outpoint.to_channel_id()));
if let Some(preimage) = htlc_update.payment_preimage {
log_trace!(logger, "Claiming HTLC with preimage {} from our monitor", preimage);
self.claim_funds_internal(htlc_update.source, preimage, htlc_update.htlc_value_satoshis.map(|v| v * 1000), true, false, counterparty_node_id, funding_outpoint);
self.claim_funds_internal(htlc_update.source, preimage, htlc_update.htlc_value_satoshis.map(|v| v * 1000), None, true, false, counterparty_node_id, funding_outpoint);
} else {
log_trace!(logger, "Failing HTLC with hash {} from our monitor", &htlc_update.payment_hash);
let receiver = HTLCDestination::NextHopChannel { node_id: counterparty_node_id, channel_id: funding_outpoint.to_channel_id() };
Expand Down Expand Up @@ -11119,7 +11127,7 @@ where
// We use `downstream_closed` in place of `from_onchain` here just as a guess - we
// don't remember in the `ChannelMonitor` where we got a preimage from, but if the
// channel is closed we just assume that it probably came from an on-chain claim.
channel_manager.claim_funds_internal(source, preimage, Some(downstream_value),
channel_manager.claim_funds_internal(source, preimage, Some(downstream_value), None,
downstream_closed, true, downstream_node_id, downstream_funding);
}

Expand Down
24 changes: 18 additions & 6 deletions lightning/src/ln/functional_test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2201,14 +2201,19 @@ macro_rules! expect_payment_path_successful {

pub fn expect_payment_forwarded<CM: AChannelManager, H: NodeHolder<CM=CM>>(
event: Event, node: &H, prev_node: &H, next_node: &H, expected_fee: Option<u64>,
upstream_force_closed: bool, downstream_force_closed: bool
expected_extra_fee_limit_msat: Option<u64>, upstream_force_closed: bool,
downstream_force_closed: bool
) {
match event {
Event::PaymentForwarded {
fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id,
outbound_amount_forwarded_msat: _
outbound_amount_forwarded_msat: _, skimmed_fee_msat
} => {
assert_eq!(fee_earned_msat, expected_fee);

// Check that the (knowingly) withheld amount is always less or equal to the expected
// overpaid amount.
assert!(skimmed_fee_msat <= expected_extra_fee_limit_msat);
if !upstream_force_closed {
// Is the event prev_channel_id in one of the channels between the two nodes?
assert!(node.node().list_channels().iter().any(|x| x.counterparty.node_id == prev_node.node().get_our_node_id() && x.channel_id == prev_channel_id.unwrap()));
Expand All @@ -2225,12 +2230,14 @@ pub fn expect_payment_forwarded<CM: AChannelManager, H: NodeHolder<CM=CM>>(
}

macro_rules! expect_payment_forwarded {
($node: expr, $prev_node: expr, $next_node: expr, $expected_fee: expr, $upstream_force_closed: expr, $downstream_force_closed: expr) => {
($node: expr, $prev_node: expr, $next_node: expr, $expected_fee: expr,
$expected_extra_fee_limit_msat: expr, $upstream_force_closed: expr,
$downstream_force_closed: expr) => {
let mut events = $node.node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
$crate::ln::functional_test_utils::expect_payment_forwarded(
events.pop().unwrap(), &$node, &$prev_node, &$next_node, $expected_fee,
$upstream_force_closed, $downstream_force_closed);
$expected_extra_fee_limit_msat, $upstream_force_closed, $downstream_force_closed);
}
}

Expand Down Expand Up @@ -2664,8 +2671,13 @@ pub fn pass_claimed_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, '
channel.context().config().forwarding_fee_base_msat
}
};
if $idx == 1 { fee += expected_extra_fees[i]; }
expect_payment_forwarded!(*$node, $next_node, $prev_node, Some(fee as u64), false, false);
let mut expected_extra_fee_limit = None;
if $idx == 1 {
fee += expected_extra_fees[i];
expected_extra_fee_limit = Some(expected_extra_fees[i] as u64);
}
expect_payment_forwarded!(*$node, $next_node, $prev_node, Some(fee as u64),
expected_extra_fee_limit, false, false);
expected_total_fee_msat += fee as u64;
check_added_monitors!($node, 1);
let new_next_msgs = if $new_msgs {
Expand Down
10 changes: 5 additions & 5 deletions lightning/src/ln/functional_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2889,7 +2889,7 @@ fn test_htlc_on_chain_success() {
}
let chan_id = Some(chan_1.2);
match forwarded_events[1] {
Event::PaymentForwarded { fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id, outbound_amount_forwarded_msat } => {
Event::PaymentForwarded { fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id, outbound_amount_forwarded_msat, .. } => {
assert_eq!(fee_earned_msat, Some(1000));
assert_eq!(prev_channel_id, chan_id);
assert_eq!(claim_from_onchain_tx, true);
Expand All @@ -2899,7 +2899,7 @@ fn test_htlc_on_chain_success() {
_ => panic!()
}
match forwarded_events[2] {
Event::PaymentForwarded { fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id, outbound_amount_forwarded_msat } => {
Event::PaymentForwarded { fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id, outbound_amount_forwarded_msat, .. } => {
assert_eq!(fee_earned_msat, Some(1000));
assert_eq!(prev_channel_id, chan_id);
assert_eq!(claim_from_onchain_tx, true);
Expand Down Expand Up @@ -4912,7 +4912,7 @@ fn test_onchain_to_onchain_claim() {
_ => panic!("Unexpected event"),
}
match events[1] {
Event::PaymentForwarded { fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id, outbound_amount_forwarded_msat } => {
Event::PaymentForwarded { fee_earned_msat, prev_channel_id, claim_from_onchain_tx, next_channel_id, outbound_amount_forwarded_msat, .. } => {
assert_eq!(fee_earned_msat, Some(1000));
assert_eq!(prev_channel_id, Some(chan_1.2));
assert_eq!(claim_from_onchain_tx, true);
Expand Down Expand Up @@ -5097,7 +5097,7 @@ fn test_duplicate_payment_hash_one_failure_one_success() {

// Solve 2nd HTLC by broadcasting on B's chain HTLC-Success Tx from C
mine_transaction(&nodes[1], &htlc_success_txn[1]);
expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], Some(196), true, true);
expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], Some(196), None, true, true);
let updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
assert!(updates.update_add_htlcs.is_empty());
assert!(updates.update_fail_htlcs.is_empty());
Expand Down Expand Up @@ -8819,7 +8819,7 @@ fn do_test_onchain_htlc_settlement_after_close(broadcast_alice: bool, go_onchain

nodes[1].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &carol_updates.update_fulfill_htlcs[0]);
let went_onchain = go_onchain_before_fulfill || force_closing_node == 1;
expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], if went_onchain { None } else { Some(1000) }, went_onchain, false);
expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], if went_onchain { None } else { Some(1000) }, None, went_onchain, false);
// If Alice broadcasted but Bob doesn't know yet, here he prepares to tell her about the preimage.
if !go_onchain_before_fulfill && broadcast_alice {
let events = nodes[1].node.get_and_clear_pending_msg_events();
Expand Down
6 changes: 3 additions & 3 deletions lightning/src/ln/payment_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -737,7 +737,7 @@ fn do_retry_with_no_persist(confirm_before_reload: bool) {
nodes[1].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &htlc_fulfill_updates.update_fulfill_htlcs[0]);
check_added_monitors!(nodes[1], 1);
commitment_signed_dance!(nodes[1], nodes[2], htlc_fulfill_updates.commitment_signed, false);
expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], None, true, false);
expect_payment_forwarded!(nodes[1], nodes[0], nodes[2], None, None, true, false);

if confirm_before_reload {
let best_block = nodes[0].blocks.lock().unwrap().last().unwrap().clone();
Expand Down Expand Up @@ -3621,7 +3621,7 @@ fn do_claim_from_closed_chan(fail_payment: bool) {

mine_transactions(&nodes[1], &[&bs_tx[0], &ds_tx[0]]);
check_added_monitors(&nodes[1], 1);
expect_payment_forwarded!(nodes[1], nodes[0], nodes[3], Some(1000), false, true);
expect_payment_forwarded!(nodes[1], nodes[0], nodes[3], Some(1000), None, false, true);

let bs_claims = nodes[1].node.get_and_clear_pending_msg_events();
check_added_monitors(&nodes[1], 1);
Expand All @@ -3640,7 +3640,7 @@ fn do_claim_from_closed_chan(fail_payment: bool) {
let cs_claim_msgs = nodes[2].node.get_and_clear_pending_msg_events();
check_added_monitors(&nodes[2], 1);
commitment_signed_dance!(nodes[2], nodes[3], updates.commitment_signed, false, true);
expect_payment_forwarded!(nodes[2], nodes[0], nodes[3], Some(1000), false, false);
expect_payment_forwarded!(nodes[2], nodes[0], nodes[3], Some(1000), None, false, false);
cs_claim_msgs
} else { panic!(); };

Expand Down

0 comments on commit d0085fd

Please sign in to comment.