Skip to content

Commit de46234

Browse files
author
Antoine Riard
committed
Introduce OnchainRequest
OnchainRequest aims to replace ClaimRequest/ClaimTxBumpMaterial with a common structure. This structure encodes a claiming packet composed from a PackageTemplate. Package attributes are available at the header-level are they used by OnchainTxHandler to make any aggregation, monitoring or bumping decisions.
1 parent 1b73c0c commit de46234

File tree

2 files changed

+190
-26
lines changed

2 files changed

+190
-26
lines changed

lightning/src/ln/onchain_utils.rs

Lines changed: 186 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -280,11 +280,9 @@ impl PackageTemplate {
280280
pub(crate) fn outpoints(&self) -> Vec<&BitcoinOutPoint> {
281281
match self {
282282
PackageTemplate::MalleableJusticeTx { ref inputs } => {
283-
assert_ne!(inputs.len(), 0);
284283
inputs.keys().collect()
285284
},
286285
PackageTemplate::RemoteHTLCTx { ref inputs } => {
287-
assert_ne!(inputs.len(), 0);
288286
inputs.keys().collect()
289287
},
290288
PackageTemplate::LocalHTLCTx { ref input } => {
@@ -299,29 +297,37 @@ impl PackageTemplate {
299297
},
300298
}
301299
}
302-
pub(crate) fn package_split(&mut self, outp: &BitcoinOutPoint) -> PackageTemplate {
303-
let package = match self {
300+
pub(crate) fn package_split(&mut self, outp: &BitcoinOutPoint) -> Option<PackageTemplate> {
301+
match self {
304302
PackageTemplate::MalleableJusticeTx { ref mut inputs } => {
305-
assert_ne!(inputs.len(), 0);
306-
let removed = inputs.remove(outp).unwrap();
307-
let mut input_splitted = HashMap::with_capacity(1);
308-
input_splitted.insert(*outp, removed);
309-
PackageTemplate::MalleableJusticeTx {
310-
inputs: input_splitted,
303+
if let Some(removed) = inputs.remove(outp) {
304+
let mut input_splitted = HashMap::with_capacity(1);
305+
input_splitted.insert(*outp, removed);
306+
return Some(PackageTemplate::MalleableJusticeTx {
307+
inputs: input_splitted,
308+
});
311309
}
310+
None
312311
},
313312
PackageTemplate::RemoteHTLCTx { ref mut inputs } => {
314-
assert_ne!(inputs.len(), 0);
315-
let removed = inputs.remove(outp).unwrap();
316-
let mut input_splitted = HashMap::with_capacity(1);
317-
input_splitted.insert(*outp, removed);
318-
PackageTemplate::RemoteHTLCTx {
319-
inputs: input_splitted,
313+
if let Some(removed) = inputs.remove(outp) {
314+
let mut input_splitted = HashMap::with_capacity(1);
315+
input_splitted.insert(*outp, removed);
316+
return Some(PackageTemplate::RemoteHTLCTx {
317+
inputs: input_splitted,
318+
});
320319
}
321-
},
322-
_ => panic!("Removing outpoints from non-malleable packages")
323-
};
324-
package
320+
None
321+
},
322+
_ => {
323+
// Note, we may try to split on remote transaction for
324+
// which we don't have a competing one (HTLC-Success before
325+
// timelock expiration). This explain we don't panic!.
326+
// We should refactor OnchainTxHandler::block_connected to
327+
// only test equality on competing claims.
328+
return None;
329+
}
330+
}
325331
}
326332
pub(crate) fn package_merge(&mut self, mut template: PackageTemplate) {
327333
match self {
@@ -413,7 +419,7 @@ impl PackageTemplate {
413419
};
414420
bumped_tx.get_weight() + witnesses_weight
415421
}
416-
pub(crate) fn package_finalize<L: Deref, ChanSigner: ChannelKeys>(&self, onchain_handler: &mut OnchainTxHandler<ChanSigner>, amount: u64, destination_script: Script, logger: &L) -> Option<Transaction>
422+
pub(crate) fn package_finalize<L: Deref, ChanSigner: ChannelKeys>(&self, onchain_handler: &mut OnchainTxHandler<ChanSigner>, value: u64, destination_script: Script, logger: &L) -> Option<Transaction>
417423
where L::Target: Logger,
418424
{
419425
let mut bumped_tx = Transaction {
@@ -422,7 +428,7 @@ impl PackageTemplate {
422428
input: vec![],
423429
output: vec![TxOut {
424430
script_pubkey: destination_script,
425-
value: 0
431+
value,
426432
}],
427433
};
428434
match self {
@@ -444,7 +450,7 @@ impl PackageTemplate {
444450
chan_utils::get_revokeable_redeemscript(&chan_keys.revocation_key, revk.on_remote_tx_csv, &chan_keys.local_delayed_payment_key)
445451
};
446452

447-
if let Ok(sig) = onchain_handler.key_storage.sign_justice_transaction(&bumped_tx, i, amount, &revk.per_commitment_key, &revk.htlc, revk.on_remote_tx_csv, &onchain_handler.secp_ctx) {
453+
if let Ok(sig) = onchain_handler.key_storage.sign_justice_transaction(&bumped_tx, i, revk.amount, &revk.per_commitment_key, &revk.htlc, revk.on_remote_tx_csv, &onchain_handler.secp_ctx) {
448454
bumped_tx.input[i].witness.push(sig.serialize_der().to_vec());
449455
bumped_tx.input[i].witness[0].push(SigHashType::All as u8);
450456
if revk.htlc.is_some() {
@@ -558,6 +564,14 @@ impl PackageTemplate {
558564
}
559565
}
560566

567+
impl Default for PackageTemplate {
568+
fn default() -> Self {
569+
PackageTemplate::MalleableJusticeTx {
570+
inputs: HashMap::new(),
571+
}
572+
}
573+
}
574+
561575
impl Writeable for PackageTemplate {
562576
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ::std::io::Error> {
563577
match self {
@@ -638,3 +652,152 @@ impl Readable for PackageTemplate {
638652
Ok(package)
639653
}
640654
}
655+
656+
/// BumpStrategy is a basic enum to encode a fee-committing strategy. We
657+
/// may extend it in the future with other stategies like BYOF-input.
658+
#[derive(PartialEq, Clone)]
659+
pub(crate) enum BumpStrategy {
660+
RBF,
661+
CPFP
662+
}
663+
664+
impl Writeable for BumpStrategy {
665+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ::std::io::Error> {
666+
match self {
667+
BumpStrategy::RBF => {
668+
writer.write_all(&[0; 1])?;
669+
},
670+
BumpStrategy::CPFP => {
671+
writer.write_all(&[1; 1])?;
672+
}
673+
}
674+
Ok(())
675+
}
676+
}
677+
678+
impl Readable for BumpStrategy {
679+
fn read<R: ::std::io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
680+
let bump_strategy = match <u8 as Readable>::read(reader)? {
681+
0 => {
682+
BumpStrategy::RBF
683+
},
684+
1 => {
685+
BumpStrategy::CPFP
686+
},
687+
_ => return Err(DecodeError::InvalidValue),
688+
};
689+
Ok(bump_strategy)
690+
}
691+
}
692+
693+
/// A structure to describe a claim content and its metadatas which is generated
694+
/// by ChannelMonitor and used by OnchainTxHandler to generate feerate-competive
695+
/// transactions.
696+
///
697+
/// Metadatas are related to multiple fields playing a role in packet lifetime.
698+
/// Once issued, it may be aggregated with other requests if it's judged safe
699+
/// and feerate opportunistic.
700+
/// Current LN fees model, pre-committed fees with update_fee adjustement, means
701+
/// that counter-signed transactions must be CPFP to be dynamically confirmed as a
702+
/// bumping strategy. If transactions aren't lockdown (i.e justice transactions) we
703+
/// may RBF them.
704+
/// Feerate previous will serve as a feerate floor between different bumping attempts.
705+
/// Height timer clocks these different bumping attempts.
706+
/// Absolute timelock defines the block barrier at which claiming isn't exclusive
707+
/// to us anymore and thus we MUST have get it solved before.
708+
/// Height original serves as a packet timestamps to prune out claim in case of reorg.
709+
/// Content embeds transactions elements to generate transaction. See PackageTemplate.
710+
#[derive(PartialEq, Clone)]
711+
pub struct OnchainRequest {
712+
// Timeout tx must have nLocktime set which means aggregating multiple
713+
// ones must take the higher nLocktime among them to satisfy all of them.
714+
// Sadly it has few pitfalls, a) it takes longuer to get fund back b) CLTV_DELTA
715+
// of a sooner-HTLC could be swallowed by the highest nLocktime of the HTLC set.
716+
// Do simplify we mark them as non-aggregable.
717+
pub(crate) aggregation: bool,
718+
// Content may lockdown with counter-signature of our counterparty
719+
// or fully-malleable by our own. Depending on this bumping strategy
720+
// must be adapted.
721+
pub(crate) bump_strategy: BumpStrategy,
722+
// Based feerate of previous broadcast. If resources available (either
723+
// output value or utxo bumping).
724+
pub(crate) feerate_previous: u64,
725+
// At every block tick, used to check if pending claiming tx is taking too
726+
// much time for confirmation and we need to bump it.
727+
pub(crate) height_timer: Option<u32>,
728+
// Block height before which claiming is exclusive to one party,
729+
// after reaching it, claiming may be contentious.
730+
pub(crate) absolute_timelock: u32,
731+
// Tracked in case of reorg to wipe out now-superflous request.
732+
pub(crate) height_original: u32,
733+
// Content of request.
734+
pub(crate) content: PackageTemplate,
735+
}
736+
737+
impl OnchainRequest {
738+
pub(crate) fn request_merge(&mut self, req: OnchainRequest) {
739+
// We init default onchain request with first merge content
740+
if self.absolute_timelock == ::std::u32::MAX {
741+
println!("Init merging {}", req.height_original);
742+
self.height_original = req.height_original;
743+
self.content = req.content;
744+
self.absolute_timelock = req.absolute_timelock;
745+
return;
746+
}
747+
assert_eq!(self.height_original, req.height_original);
748+
if self.absolute_timelock > req.absolute_timelock {
749+
self.absolute_timelock = req.absolute_timelock;
750+
}
751+
self.content.package_merge(req.content);
752+
}
753+
}
754+
755+
impl Default for OnchainRequest {
756+
fn default() -> Self {
757+
OnchainRequest {
758+
aggregation: true,
759+
bump_strategy: BumpStrategy::RBF,
760+
feerate_previous: 0,
761+
height_timer: None,
762+
absolute_timelock: ::std::u32::MAX,
763+
height_original: 0,
764+
content: PackageTemplate::default()
765+
}
766+
}
767+
}
768+
769+
impl Writeable for OnchainRequest {
770+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ::std::io::Error> {
771+
self.aggregation.write(writer)?;
772+
self.bump_strategy.write(writer)?;
773+
self.feerate_previous.write(writer)?;
774+
self.height_timer.write(writer)?;
775+
self.absolute_timelock.write(writer)?;
776+
self.height_original.write(writer)?;
777+
self.content.write(writer)?;
778+
779+
Ok(())
780+
}
781+
}
782+
783+
impl Readable for OnchainRequest {
784+
fn read<R: ::std::io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
785+
let aggregation = Readable::read(reader)?;
786+
let bump_strategy = Readable::read(reader)?;
787+
let feerate_previous = Readable::read(reader)?;
788+
let height_timer = Readable::read(reader)?;
789+
let absolute_timelock = Readable::read(reader)?;
790+
let height_original = Readable::read(reader)?;
791+
let content = Readable::read(reader)?;
792+
793+
Ok(OnchainRequest {
794+
aggregation,
795+
bump_strategy,
796+
feerate_previous,
797+
height_timer,
798+
absolute_timelock,
799+
height_original,
800+
content
801+
})
802+
}
803+
}

lightning/src/ln/onchaintx.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -546,9 +546,10 @@ impl<ChanSigner: ChannelKeys> OnchainTxHandler<ChanSigner> {
546546
} else { // If false, generate new claim request with update outpoint set
547547
let mut at_least_one_drop = false;
548548
for input in tx.input.iter() {
549-
let package = claim_material.package_template.package_split(&input.previous_output);
550-
claimed_outputs_material.push(package);
551-
at_least_one_drop = true;
549+
if let Some(package) = claim_material.package_template.package_split(&input.previous_output) {
550+
claimed_outputs_material.push(package);
551+
at_least_one_drop = true;
552+
}
552553
// If there are no outpoints left to claim in this request, drop it entirely after ANTI_REORG_DELAY.
553554
if claim_material.package_template.outpoints().is_empty() {
554555
clean_claim_request_after_safety_delay!();

0 commit comments

Comments
 (0)