Skip to content

Commit 107eb25

Browse files
committed
[wip] Implement Holder HTLC claim chunking for 0FC channels
1 parent 19a9dbd commit 107eb25

File tree

2 files changed

+185
-13
lines changed

2 files changed

+185
-13
lines changed

lightning/src/events/bump_transaction/mod.rs

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ use crate::util::logger::Logger;
4141
use bitcoin::amount::Amount;
4242
use bitcoin::consensus::Encodable;
4343
use bitcoin::constants::WITNESS_SCALE_FACTOR;
44+
use bitcoin::hashes::sha256::Hash as Sha256;
45+
use bitcoin::hashes::{Hash, HashEngine};
4446
use bitcoin::locktime::absolute::LockTime;
4547
use bitcoin::secp256k1;
4648
use bitcoin::secp256k1::ecdsa::Signature;
@@ -983,7 +985,7 @@ where
983985
// Our estimate should be within a 1% error margin of the actual weight and we should
984986
// never underestimate.
985987
assert!(expected_signed_tx_weight >= signed_tx_weight);
986-
assert!(expected_signed_tx_weight * 99 / 100 <= signed_tx_weight);
988+
assert!(expected_signed_tx_weight * 98 / 100 <= signed_tx_weight);
987989

988990
let expected_signed_tx_fee =
989991
fee_for_weight(target_feerate_sat_per_1000_weight, signed_tx_weight);
@@ -1045,20 +1047,63 @@ where
10451047
log_bytes!(claim_id.0),
10461048
log_iter!(htlc_descriptors.iter().map(|d| d.outpoint()))
10471049
);
1048-
self.handle_htlc_resolution(
1049-
*claim_id,
1050-
*target_feerate_sat_per_1000_weight,
1051-
htlc_descriptors,
1052-
*tx_lock_time,
1053-
)
1054-
.await
1055-
.unwrap_or_else(|_| {
1056-
log_error!(
1050+
let channel_type = &htlc_descriptors[0]
1051+
.channel_derivation_parameters
1052+
.transaction_parameters
1053+
.channel_type_features;
1054+
let htlc_chunks_size = if channel_type.supports_anchor_zero_fee_commitments() {
1055+
// Cap the size of transactions claiming `HolderHTLCOutput` in 0FC channels.
1056+
// Otherwise, we could hit the max 10_000vB size limit on V3 transactions
1057+
// (BIP 431 rule 4).
1058+
25
1059+
} else {
1060+
htlc_descriptors.len()
1061+
};
1062+
for htlc_descriptors in htlc_descriptors.chunks(htlc_chunks_size) {
1063+
let utxo_id = if channel_type.supports_anchor_zero_fee_commitments() {
1064+
// Generate a new claim_id to map a user-provided utxo to this
1065+
// particular set of HTLCs via `select_confirmed_utxos`; matches the
1066+
// scheme used in `onchain`.
1067+
//
1068+
// In rare cases, a HTLC set can change if a counterparty claims a
1069+
// subset of the HTLCs in the set. In that case we regenerate a new set
1070+
// of HTLCs, a new claim_id, and ask the user for a new UTXO to confirm
1071+
// this new set. Users should be able to provide the UTXO that was
1072+
// previously assigned to the now double-spent transaction to confirm
1073+
// this new set.
1074+
let mut engine = Sha256::engine();
1075+
for htlc in htlc_descriptors {
1076+
engine.input(&htlc.commitment_txid.to_byte_array());
1077+
engine
1078+
.input(&htlc.htlc.transaction_output_index.unwrap().to_be_bytes());
1079+
}
1080+
ClaimId(Sha256::from_engine(engine).to_byte_array())
1081+
} else {
1082+
// Non-0FC channels batch the full set of HTLCs in the claim into a
1083+
// single transaction, so we re-use the claim_id as the UTXO id here.
1084+
*claim_id
1085+
};
1086+
log_info!(
10571087
self.logger,
1058-
"Failed bumping HTLC transaction fee for commitment {}",
1059-
htlc_descriptors[0].commitment_txid
1088+
"Batch transaction assigned to UTXO id {} contains these HTLCs: {}",
1089+
log_bytes!(utxo_id.0),
1090+
log_iter!(htlc_descriptors.iter().map(|d| d.outpoint()))
10601091
);
1061-
});
1092+
self.handle_htlc_resolution(
1093+
utxo_id,
1094+
*target_feerate_sat_per_1000_weight,
1095+
htlc_descriptors,
1096+
*tx_lock_time,
1097+
)
1098+
.await
1099+
.unwrap_or_else(|_| {
1100+
log_error!(
1101+
self.logger,
1102+
"Failed bumping HTLC transaction fee for commitment {}",
1103+
htlc_descriptors[0].commitment_txid
1104+
);
1105+
});
1106+
}
10621107
},
10631108
}
10641109
}

lightning/src/ln/zero_fee_commitment_tests.rs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
use crate::events::{ClosureReason, Event};
12
use crate::ln::chan_utils::shared_anchor_script_pubkey;
23
use crate::ln::functional_test_utils::*;
4+
use crate::ln::msgs::BaseMessageHandler;
35

46
#[test]
57
fn test_p2a_anchor_values_under_trims_and_rounds() {
@@ -92,3 +94,128 @@ fn test_p2a_anchor_values_under_trims_and_rounds() {
9294
p2a_value_test!([353_000], [353_001], 240);
9395
p2a_value_test!([353_001], [353_001], 240);
9496
}
97+
98+
#[test]
99+
fn test_htlc_claim_chunking() {
100+
let chanmon_cfgs = create_chanmon_cfgs(2);
101+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
102+
let mut user_cfg = test_default_channel_config();
103+
user_cfg.channel_handshake_config.our_htlc_minimum_msat = 1;
104+
user_cfg.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true;
105+
user_cfg.manually_accept_inbound_channels = true;
106+
107+
let configs = [Some(user_cfg.clone()), Some(user_cfg)];
108+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &configs);
109+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
110+
111+
let coinbase_tx = provide_anchor_reserves(&nodes);
112+
113+
let _node_a_id = nodes[0].node.get_our_node_id();
114+
let _node_b_id = nodes[1].node.get_our_node_id();
115+
116+
const CHAN_CAPACITY: u64 = 10_000_000;
117+
let (_, _, chan_id, _funding_tx) = create_announced_chan_between_nodes_with_value(
118+
&nodes,
119+
0,
120+
1,
121+
CHAN_CAPACITY,
122+
(CHAN_CAPACITY / 2) * 1000,
123+
);
124+
125+
let mut node_1_preimages = Vec::new();
126+
const NONDUST_HTLC_AMT_MSAT: u64 = 1_000_000;
127+
for _ in 0..26 {
128+
let (preimage, payment_hash, _, _) =
129+
route_payment(&nodes[0], &[&nodes[1]], NONDUST_HTLC_AMT_MSAT);
130+
node_1_preimages.push((preimage, payment_hash));
131+
}
132+
let node_0_commit_tx = get_local_commitment_txn!(nodes[0], chan_id);
133+
assert_eq!(node_0_commit_tx.len(), 1);
134+
assert_eq!(node_0_commit_tx[0].output.len(), 26 + 2 + 1);
135+
let node_1_commit_tx = get_local_commitment_txn!(nodes[1], chan_id);
136+
assert_eq!(node_1_commit_tx.len(), 1);
137+
assert_eq!(node_1_commit_tx[0].output.len(), 26 + 2 + 1);
138+
139+
for (preimage, payment_hash) in node_1_preimages {
140+
nodes[1].node.claim_funds(preimage);
141+
check_added_monitors!(nodes[1], 1);
142+
expect_payment_claimed!(nodes[1], payment_hash, NONDUST_HTLC_AMT_MSAT);
143+
}
144+
nodes[0].node.get_and_clear_pending_msg_events();
145+
nodes[1].node.get_and_clear_pending_msg_events();
146+
147+
mine_transaction(&nodes[0], &node_1_commit_tx[0]);
148+
mine_transaction(&nodes[1], &node_1_commit_tx[0]);
149+
150+
let mut events = nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events();
151+
assert_eq!(events.len(), 1);
152+
match events.pop().unwrap() {
153+
Event::BumpTransaction(bump_event) => {
154+
nodes[1].bump_tx_handler.handle_event(&bump_event);
155+
},
156+
_ => panic!("Unexpected event"),
157+
}
158+
159+
let htlc_claims = nodes[1].tx_broadcaster.txn_broadcast();
160+
assert_eq!(htlc_claims.len(), 2);
161+
162+
check_spends!(htlc_claims[0], node_1_commit_tx[0], coinbase_tx);
163+
check_spends!(htlc_claims[1], node_1_commit_tx[0], coinbase_tx);
164+
165+
assert_eq!(htlc_claims[0].input.len(), 26);
166+
assert_eq!(htlc_claims[0].output.len(), 26);
167+
assert_eq!(htlc_claims[1].input.len(), 2);
168+
assert_eq!(htlc_claims[1].output.len(), 2);
169+
170+
check_closed_broadcast!(nodes[0], true);
171+
check_added_monitors!(nodes[0], 1);
172+
check_closed_event!(
173+
nodes[0],
174+
1,
175+
ClosureReason::CommitmentTxConfirmed,
176+
[nodes[1].node.get_our_node_id()],
177+
CHAN_CAPACITY
178+
);
179+
assert!(nodes[0].node.list_channels().is_empty());
180+
check_closed_broadcast!(nodes[1], true);
181+
check_added_monitors!(nodes[1], 1);
182+
check_closed_event!(
183+
nodes[1],
184+
1,
185+
ClosureReason::CommitmentTxConfirmed,
186+
[nodes[0].node.get_our_node_id()],
187+
CHAN_CAPACITY
188+
);
189+
assert!(nodes[1].node.list_channels().is_empty());
190+
assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
191+
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
192+
193+
mine_transaction(&nodes[1], &htlc_claims[0]);
194+
195+
let mut events = nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events();
196+
assert_eq!(events.len(), 1);
197+
match events.pop().unwrap() {
198+
Event::BumpTransaction(bump_event) => {
199+
nodes[1].bump_tx_handler.handle_event(&bump_event);
200+
},
201+
_ => panic!("Unexpected event"),
202+
}
203+
204+
let fresh_htlc_claims = nodes[1].tx_broadcaster.txn_broadcast();
205+
assert_eq!(fresh_htlc_claims.len(), 1);
206+
check_spends!(fresh_htlc_claims[0], node_1_commit_tx[0], htlc_claims[0]);
207+
assert_eq!(fresh_htlc_claims[0].input.len(), 2);
208+
assert_eq!(fresh_htlc_claims[0].output.len(), 2);
209+
210+
// Assert when we handled the second `BumpTransaction::HTLCResolution` event, we
211+
// assigned the same UTXO id to that set of HTLCs
212+
let log_entries = &nodes[1].logger.lines.lock().unwrap();
213+
let keys: Vec<_> = log_entries
214+
.keys()
215+
.filter(|key| key.1.contains("Batch transaction assigned to UTXO id"))
216+
.collect();
217+
assert_eq!(keys.len(), 2);
218+
let values = [*log_entries.get(keys[0]).unwrap(), *log_entries.get(keys[1]).unwrap()];
219+
assert!(values.contains(&1));
220+
assert!(values.contains(&2));
221+
}

0 commit comments

Comments
 (0)