Skip to content

Commit a626281

Browse files
authored
Remove duplication around skipping dust transactions (#3068)
We have a lot of duplicated code where we skip creating 2nd-stage and 3rd-stage transactions when their output would be below dust. We now introduce a helper function to factorize this shared behavior.
1 parent ecd4634 commit a626281

File tree

1 file changed

+85
-107
lines changed

1 file changed

+85
-107
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala

Lines changed: 85 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -589,20 +589,16 @@ object Transactions {
589589
} match {
590590
case Some(outputIndex) =>
591591
val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), write(redeemScript))
592-
// unsigned tx
593-
val tx = Transaction(
592+
val unsignedTx = Transaction(
594593
version = 2,
595594
txIn = TxIn(input.outPoint, ByteVector.empty, getHtlcTxInputSequence(commitmentFormat)) :: Nil,
596-
txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil,
597-
lockTime = 0)
598-
val weight = addSigs(ClaimHtlcSuccessTx(input, tx, htlc.paymentHash, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong))), PlaceHolderSig, ByteVector32.Zeroes).tx.weight()
599-
val fee = weight2fee(feeratePerKw, weight)
600-
val amount = input.txOut.amount - fee
601-
if (amount < dustLimit) {
602-
Left(AmountBelowDustLimit)
603-
} else {
604-
val tx1 = tx.copy(txOut = tx.txOut.head.copy(amount = amount) :: Nil)
605-
Right(ClaimHtlcSuccessTx(input, tx1, htlc.paymentHash, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong))))
595+
txOut = TxOut(0 sat, localFinalScriptPubKey) :: Nil,
596+
lockTime = 0
597+
)
598+
val dummySignedTx = addSigs(ClaimHtlcSuccessTx(input, unsignedTx, htlc.paymentHash, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong))), PlaceHolderSig, ByteVector32.Zeroes)
599+
skipTxIfBelowDust(dummySignedTx, feeratePerKw, dustLimit).map { amount =>
600+
val tx = unsignedTx.copy(txOut = TxOut(amount, localFinalScriptPubKey) :: Nil)
601+
ClaimHtlcSuccessTx(input, tx, htlc.paymentHash, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong)))
606602
}
607603
case None => Left(OutputNotFound)
608604
}
@@ -622,20 +618,16 @@ object Transactions {
622618
} match {
623619
case Some(outputIndex) =>
624620
val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), write(redeemScript))
625-
// unsigned tx
626-
val tx = Transaction(
621+
val unsignedTx = Transaction(
627622
version = 2,
628623
txIn = TxIn(input.outPoint, ByteVector.empty, getHtlcTxInputSequence(commitmentFormat)) :: Nil,
629-
txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil,
630-
lockTime = htlc.cltvExpiry.toLong)
631-
val weight = addSigs(ClaimHtlcTimeoutTx(input, tx, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong))), PlaceHolderSig).tx.weight()
632-
val fee = weight2fee(feeratePerKw, weight)
633-
val amount = input.txOut.amount - fee
634-
if (amount < dustLimit) {
635-
Left(AmountBelowDustLimit)
636-
} else {
637-
val tx1 = tx.copy(txOut = tx.txOut.head.copy(amount = amount) :: Nil)
638-
Right(ClaimHtlcTimeoutTx(input, tx1, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong))))
624+
txOut = TxOut(0 sat, localFinalScriptPubKey) :: Nil,
625+
lockTime = htlc.cltvExpiry.toLong
626+
)
627+
val dummySignedTx = addSigs(ClaimHtlcTimeoutTx(input, unsignedTx, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong))), PlaceHolderSig)
628+
skipTxIfBelowDust(dummySignedTx, feeratePerKw, dustLimit).map { amount =>
629+
val tx = unsignedTx.copy(txOut = TxOut(amount, localFinalScriptPubKey) :: Nil)
630+
ClaimHtlcTimeoutTx(input, tx, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong)))
639631
}
640632
case None => Left(OutputNotFound)
641633
}
@@ -648,21 +640,16 @@ object Transactions {
648640
case Left(skip) => Left(skip)
649641
case Right(outputIndex) =>
650642
val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), write(redeemScript))
651-
// unsigned tx
652-
val tx = Transaction(
643+
val unsignedTx = Transaction(
653644
version = 2,
654-
txIn = TxIn(input.outPoint, ByteVector.empty, 0x00000000L) :: Nil,
655-
txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil,
656-
lockTime = 0)
657-
// compute weight with a dummy 73 bytes signature (the largest you can get)
658-
val weight = addSigs(ClaimP2WPKHOutputTx(input, tx), keys, PlaceHolderSig).tx.weight()
659-
val fee = weight2fee(feeratePerKw, weight)
660-
val amount = input.txOut.amount - fee
661-
if (amount < localDustLimit) {
662-
Left(AmountBelowDustLimit)
663-
} else {
664-
val tx1 = tx.copy(txOut = tx.txOut.head.copy(amount = amount) :: Nil)
665-
Right(ClaimP2WPKHOutputTx(input, tx1))
645+
txIn = TxIn(input.outPoint, ByteVector.empty, 0) :: Nil,
646+
txOut = TxOut(0 sat, localFinalScriptPubKey) :: Nil,
647+
lockTime = 0
648+
)
649+
val dummySignedTx = addSigs(ClaimP2WPKHOutputTx(input, unsignedTx), keys, PlaceHolderSig)
650+
skipTxIfBelowDust(dummySignedTx, feeratePerKw, localDustLimit).map { amount =>
651+
val tx = unsignedTx.copy(txOut = TxOut(amount, localFinalScriptPubKey) :: Nil)
652+
ClaimP2WPKHOutputTx(input, tx)
666653
}
667654
}
668655
}
@@ -674,21 +661,16 @@ object Transactions {
674661
case Left(skip) => Left(skip)
675662
case Right(outputIndex) =>
676663
val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), write(redeemScript))
677-
// unsigned transaction
678-
val tx = Transaction(
664+
val unsignedTx = Transaction(
679665
version = 2,
680666
txIn = TxIn(input.outPoint, ByteVector.empty, 1) :: Nil,
681-
txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil,
682-
lockTime = 0)
683-
// compute weight with a dummy 73 bytes signature (the largest you can get)
684-
val weight = addSigs(ClaimRemoteDelayedOutputTx(input, tx), PlaceHolderSig).tx.weight()
685-
val fee = weight2fee(feeratePerKw, weight)
686-
val amount = input.txOut.amount - fee
687-
if (amount < localDustLimit) {
688-
Left(AmountBelowDustLimit)
689-
} else {
690-
val tx1 = tx.copy(txOut = tx.txOut.head.copy(amount = amount) :: Nil)
691-
Right(ClaimRemoteDelayedOutputTx(input, tx1))
667+
txOut = TxOut(0 sat, localFinalScriptPubKey) :: Nil,
668+
lockTime = 0
669+
)
670+
val dummySignedTx = addSigs(ClaimRemoteDelayedOutputTx(input, unsignedTx), PlaceHolderSig)
671+
skipTxIfBelowDust(dummySignedTx, feeratePerKw, localDustLimit).map { amount =>
672+
val tx = unsignedTx.copy(txOut = TxOut(amount, localFinalScriptPubKey) :: Nil)
673+
ClaimRemoteDelayedOutputTx(input, tx)
692674
}
693675
}
694676
}
@@ -712,21 +694,16 @@ object Transactions {
712694
case Left(skip) => Left(skip)
713695
case Right(outputIndex) =>
714696
val input = InputInfo(OutPoint(parentTx, outputIndex), parentTx.txOut(outputIndex), write(redeemScript))
715-
// unsigned transaction
716-
val tx = Transaction(
697+
val unsignedTx = Transaction(
717698
version = 2,
718699
txIn = TxIn(input.outPoint, ByteVector.empty, toLocalDelay.toInt) :: Nil,
719-
txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil,
720-
lockTime = 0)
721-
// compute weight with a dummy 73 bytes signature (the largest you can get)
722-
val weight = addSigs(ClaimLocalDelayedOutputTx(input, tx), PlaceHolderSig).tx.weight()
723-
val fee = weight2fee(feeratePerKw, weight)
724-
val amount = input.txOut.amount - fee
725-
if (amount < localDustLimit) {
726-
Left(AmountBelowDustLimit)
727-
} else {
728-
val tx1 = tx.copy(txOut = tx.txOut.head.copy(amount = amount) :: Nil)
729-
Right(input, tx1)
700+
txOut = TxOut(0 sat, localFinalScriptPubKey) :: Nil,
701+
lockTime = 0
702+
)
703+
val dummySignedTx = addSigs(ClaimLocalDelayedOutputTx(input, unsignedTx), PlaceHolderSig)
704+
skipTxIfBelowDust(dummySignedTx, feeratePerKw, localDustLimit).map { amount =>
705+
val tx = unsignedTx.copy(txOut = TxOut(amount, localFinalScriptPubKey) :: Nil)
706+
(input, tx)
730707
}
731708
}
732709
}
@@ -738,13 +715,13 @@ object Transactions {
738715
case Left(skip) => Left(skip)
739716
case Right(outputIndex) =>
740717
val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), write(redeemScript))
741-
// unsigned transaction
742-
val tx = Transaction(
718+
val unsignedTx = Transaction(
743719
version = 2,
744720
txIn = TxIn(input.outPoint, ByteVector.empty, 0) :: Nil,
745721
txOut = Nil, // anchor is only used to bump fees, the output will be added later depending on available inputs
746-
lockTime = 0)
747-
Right(ClaimAnchorOutputTx(input, tx, confirmationTarget))
722+
lockTime = 0
723+
)
724+
Right(ClaimAnchorOutputTx(input, unsignedTx, confirmationTarget))
748725
}
749726
}
750727

@@ -755,21 +732,16 @@ object Transactions {
755732
case Left(skip) => Seq(Left(skip))
756733
case Right(outputIndexes) => outputIndexes.map(outputIndex => {
757734
val input = InputInfo(OutPoint(htlcTx, outputIndex), htlcTx.txOut(outputIndex), write(redeemScript))
758-
// unsigned transaction
759-
val tx = Transaction(
735+
val unsignedTx = Transaction(
760736
version = 2,
761737
txIn = TxIn(input.outPoint, ByteVector.empty, 0xffffffffL) :: Nil,
762-
txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil,
763-
lockTime = 0)
764-
// compute weight with a dummy 73 bytes signature (the largest you can get)
765-
val weight = addSigs(ClaimHtlcDelayedOutputPenaltyTx(input, tx), PlaceHolderSig).tx.weight()
766-
val fee = weight2fee(feeratePerKw, weight)
767-
val amount = input.txOut.amount - fee
768-
if (amount < localDustLimit) {
769-
Left(AmountBelowDustLimit)
770-
} else {
771-
val tx1 = tx.copy(txOut = tx.txOut.head.copy(amount = amount) :: Nil)
772-
Right(ClaimHtlcDelayedOutputPenaltyTx(input, tx1))
738+
txOut = TxOut(0 sat, localFinalScriptPubKey) :: Nil,
739+
lockTime = 0
740+
)
741+
val dummySignedTx = addSigs(ClaimHtlcDelayedOutputPenaltyTx(input, unsignedTx), PlaceHolderSig)
742+
skipTxIfBelowDust(dummySignedTx, feeratePerKw, localDustLimit).map { amount =>
743+
val tx = unsignedTx.copy(txOut = TxOut(amount, localFinalScriptPubKey) :: Nil)
744+
ClaimHtlcDelayedOutputPenaltyTx(input, tx)
773745
}
774746
})
775747
}
@@ -782,42 +754,32 @@ object Transactions {
782754
case Left(skip) => Left(skip)
783755
case Right(outputIndex) =>
784756
val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), write(redeemScript))
785-
// unsigned transaction
786-
val tx = Transaction(
757+
val unsignedTx = Transaction(
787758
version = 2,
788759
txIn = TxIn(input.outPoint, ByteVector.empty, 0xffffffffL) :: Nil,
789-
txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil,
790-
lockTime = 0)
791-
// compute weight with a dummy 73 bytes signature (the largest you can get)
792-
val weight = addSigs(MainPenaltyTx(input, tx), PlaceHolderSig).tx.weight()
793-
val fee = weight2fee(feeratePerKw, weight)
794-
val amount = input.txOut.amount - fee
795-
if (amount < localDustLimit) {
796-
Left(AmountBelowDustLimit)
797-
} else {
798-
val tx1 = tx.copy(txOut = tx.txOut.head.copy(amount = amount) :: Nil)
799-
Right(MainPenaltyTx(input, tx1))
760+
txOut = TxOut(0 sat, localFinalScriptPubKey) :: Nil,
761+
lockTime = 0
762+
)
763+
val dummySignedTx = addSigs(MainPenaltyTx(input, unsignedTx), PlaceHolderSig)
764+
skipTxIfBelowDust(dummySignedTx, feeratePerKw, localDustLimit).map { amount =>
765+
val tx = unsignedTx.copy(txOut = TxOut(amount, localFinalScriptPubKey) :: Nil)
766+
MainPenaltyTx(input, tx)
800767
}
801768
}
802769
}
803770

804771
def makeHtlcPenaltyTx(commitTx: Transaction, htlcOutputIndex: Int, redeemScript: ByteVector, localDustLimit: Satoshi, localFinalScriptPubKey: ByteVector, feeratePerKw: FeeratePerKw): Either[TxGenerationSkipped, HtlcPenaltyTx] = {
805772
val input = InputInfo(OutPoint(commitTx, htlcOutputIndex), commitTx.txOut(htlcOutputIndex), redeemScript)
806-
// unsigned transaction
807-
val tx = Transaction(
773+
val unsignedTx = Transaction(
808774
version = 2,
809775
txIn = TxIn(input.outPoint, ByteVector.empty, 0xffffffffL) :: Nil,
810-
txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil,
811-
lockTime = 0)
812-
// compute weight with a dummy 73 bytes signature (the largest you can get)
813-
val weight = addSigs(MainPenaltyTx(input, tx), PlaceHolderSig).tx.weight()
814-
val fee = weight2fee(feeratePerKw, weight)
815-
val amount = input.txOut.amount - fee
816-
if (amount < localDustLimit) {
817-
Left(AmountBelowDustLimit)
818-
} else {
819-
val tx1 = tx.copy(txOut = tx.txOut.head.copy(amount = amount) :: Nil)
820-
Right(HtlcPenaltyTx(input, tx1))
776+
txOut = TxOut(0 sat, localFinalScriptPubKey) :: Nil,
777+
lockTime = 0
778+
)
779+
val dummySignedTx = addSigs(MainPenaltyTx(input, unsignedTx), PlaceHolderSig)
780+
skipTxIfBelowDust(dummySignedTx, feeratePerKw, localDustLimit).map { amount =>
781+
val tx = unsignedTx.copy(txOut = TxOut(amount, localFinalScriptPubKey) :: Nil)
782+
HtlcPenaltyTx(input, tx)
821783
}
822784
}
823785

@@ -913,6 +875,22 @@ object Transactions {
913875
}
914876
}
915877

878+
/**
879+
* We skip creating transactions spending commitment outputs when the remaining amount is below dust.
880+
*
881+
* @param dummySignedTx the transaction with a witness filled with dummy signatures (to compute its weight).
882+
* @return the output amount, unless the transaction should be skipped because it's below dust.
883+
*/
884+
private def skipTxIfBelowDust(dummySignedTx: TransactionWithInputInfo, feerate: FeeratePerKw, dustLimit: Satoshi): Either[TxGenerationSkipped, Satoshi] = {
885+
val fee = weight2fee(feerate, dummySignedTx.tx.weight())
886+
val amount = dummySignedTx.input.txOut.amount - fee
887+
if (amount < dustLimit) {
888+
Left(AmountBelowDustLimit)
889+
} else {
890+
Right(amount)
891+
}
892+
}
893+
916894
def findPubKeyScriptIndex(tx: Transaction, pubkeyScript: ByteVector): Either[TxGenerationSkipped, Int] = {
917895
val outputIndex = tx.txOut.indexWhere(_.publicKeyScript == pubkeyScript)
918896
if (outputIndex >= 0) {

0 commit comments

Comments
 (0)