Skip to content

Commit 92f1147

Browse files
committed
Delay broadcast of PackageTemplate packages until their locktime
This stores transaction templates temporarily until their locktime is reached, avoiding broadcasting (or RBF bumping) transactions prior to their locktime. For those broadcasting transactions (potentially indirectly) via Bitcoin Core RPC, this ensures no automated rebroadcast of transactions on the client side is required to get transactions confirmed.
1 parent 191a887 commit 92f1147

File tree

3 files changed

+308
-210
lines changed

3 files changed

+308
-210
lines changed

lightning/src/chain/onchaintx.rs

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use util::logger::Logger;
3232
use util::ser::{Readable, ReadableArgs, Writer, Writeable, VecWriter};
3333
use util::byte_utils;
3434

35-
use std::collections::HashMap;
35+
use std::collections::{BTreeMap, HashMap};
3636
use core::cmp;
3737
use core::ops::Deref;
3838
use core::mem::replace;
@@ -164,8 +164,9 @@ pub struct OnchainTxHandler<ChannelSigner: Sign> {
164164
#[cfg(not(test))]
165165
claimable_outpoints: HashMap<BitcoinOutPoint, (Txid, u32)>,
166166

167-
onchain_events_awaiting_threshold_conf: Vec<OnchainEventEntry>,
167+
locktimed_packages: BTreeMap<u32, Vec<PackageTemplate>>,
168168

169+
onchain_events_awaiting_threshold_conf: Vec<OnchainEventEntry>,
169170

170171
pub(super) secp_ctx: Secp256k1<secp256k1::All>,
171172
}
@@ -205,6 +206,15 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
205206
claim_and_height.1.write(writer)?;
206207
}
207208

209+
writer.write_all(&byte_utils::be64_to_array(self.locktimed_packages.len() as u64))?;
210+
for (ref locktime, ref packages) in self.locktimed_packages.iter() {
211+
locktime.write(writer)?;
212+
writer.write_all(&byte_utils::be64_to_array(packages.len() as u64))?;
213+
for ref package in packages.iter() {
214+
package.write(writer)?;
215+
}
216+
}
217+
208218
writer.write_all(&byte_utils::be64_to_array(self.onchain_events_awaiting_threshold_conf.len() as u64))?;
209219
for ref entry in self.onchain_events_awaiting_threshold_conf.iter() {
210220
entry.txid.write(writer)?;
@@ -264,6 +274,19 @@ impl<'a, K: KeysInterface> ReadableArgs<&'a K> for OnchainTxHandler<K::Signer> {
264274
let height = Readable::read(reader)?;
265275
claimable_outpoints.insert(outpoint, (ancestor_claim_txid, height));
266276
}
277+
278+
let locktimed_packages_len: u64 = Readable::read(reader)?;
279+
let mut locktimed_packages = BTreeMap::new();
280+
for _ in 0..locktimed_packages_len {
281+
let locktime = Readable::read(reader)?;
282+
let packages_len: u64 = Readable::read(reader)?;
283+
let mut packages = Vec::with_capacity(cmp::min(packages_len as usize, MAX_ALLOC_SIZE / std::mem::size_of::<PackageTemplate>()));
284+
for _ in 0..packages_len {
285+
packages.push(Readable::read(reader)?);
286+
}
287+
locktimed_packages.insert(locktime, packages);
288+
}
289+
267290
let waiting_threshold_conf_len: u64 = Readable::read(reader)?;
268291
let mut onchain_events_awaiting_threshold_conf = Vec::with_capacity(cmp::min(waiting_threshold_conf_len as usize, MAX_ALLOC_SIZE / 128));
269292
for _ in 0..waiting_threshold_conf_len {
@@ -301,6 +324,7 @@ impl<'a, K: KeysInterface> ReadableArgs<&'a K> for OnchainTxHandler<K::Signer> {
301324
signer,
302325
channel_transaction_parameters: channel_parameters,
303326
claimable_outpoints,
327+
locktimed_packages,
304328
pending_claim_requests,
305329
onchain_events_awaiting_threshold_conf,
306330
secp_ctx,
@@ -320,6 +344,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
320344
channel_transaction_parameters: channel_parameters,
321345
pending_claim_requests: HashMap::new(),
322346
claimable_outpoints: HashMap::new(),
347+
locktimed_packages: BTreeMap::new(),
323348
onchain_events_awaiting_threshold_conf: Vec::new(),
324349

325350
secp_ctx,
@@ -375,9 +400,17 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
375400

376401
// Try to aggregate outputs if their timelock expiration isn't imminent (package timelock
377402
// <= CLTV_SHARED_CLAIM_BUFFER) and they don't require an immediate nLockTime (aggregable).
378-
for req in requests {
403+
'req_walk: for req in requests {
379404
// Don't claim a outpoint twice that would be bad for privacy and may uselessly lock a CPFP input for a while
380-
if let Some(_) = self.claimable_outpoints.get(req.outpoints()[0]) { log_trace!(logger, "Bouncing off outpoint {}:{}, already registered its claiming request", req.outpoints()[0].txid, req.outpoints()[0].vout); } else {
405+
if let Some(_) = self.claimable_outpoints.get(req.outpoints()[0]) {
406+
log_trace!(logger, "Bouncing off outpoint {}:{}, already registered its claiming request", req.outpoints()[0].txid, req.outpoints()[0].vout);
407+
} else {
408+
for locked_package in self.locktimed_packages.iter().map(|v| v.1.iter()).flatten() {
409+
if locked_package.outpoints() == req.outpoints() {
410+
continue 'req_walk;
411+
}
412+
}
413+
381414
log_trace!(logger, "Test if outpoint can be aggregated with expiration {} against {}", req.timelock(), height + CLTV_SHARED_CLAIM_BUFFER);
382415
if req.timelock() <= height + CLTV_SHARED_CLAIM_BUFFER || !req.aggregable() {
383416
// Don't aggregate if outpoint package timelock is soon or marked as non-aggregable
@@ -393,10 +426,26 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
393426
preprocessed_requests.push(req);
394427
}
395428

429+
loop {
430+
let pop_height = if let Some(first_entry) = self.locktimed_packages.iter().next() {
431+
if *first_entry.0 <= height + 1 {
432+
*first_entry.0
433+
} else { break; }
434+
} else { break; };
435+
log_trace!(logger, "Restoring delayed claim of package(s) at their timelock at {}.", pop_height);
436+
preprocessed_requests.append(&mut self.locktimed_packages.remove(&pop_height).unwrap());
437+
}
438+
396439
// Generate claim transactions and track them to bump if necessary at
397440
// height timer expiration (i.e in how many blocks we're going to take action).
398441
for mut req in preprocessed_requests {
399-
if let Some((new_timer, new_feerate, tx)) = self.generate_claim_tx(height, &req, &*fee_estimator, &*logger) {
442+
if req.package_timelock() > height + 1 {
443+
log_debug!(logger, "Delaying claim of package until its timelock at {} (current height {}), the following outpoints are spent:", req.package_timelock(), height);
444+
for outpoint in req.outpoints() {
445+
log_debug!(logger, " Outpoint {}", outpoint);
446+
}
447+
self.locktimed_packages.entry(req.package_timelock()).or_insert(Vec::new()).push(req);
448+
} else if let Some((new_timer, new_feerate, tx)) = self.generate_claim_tx(height, &req, &*fee_estimator, &*logger) {
400449
req.set_timer(new_timer);
401450
req.set_feerate(new_feerate);
402451
let txid = tx.txid();

0 commit comments

Comments
 (0)