Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
import fr.acinq.bitcoin.scalacompat.{ByteVector64, DeterministicWallet, OutPoint, Satoshi, SatoshiLong, Script, ScriptWitness, Transaction, TxIn, TxOut, addressToPublicKeyScript}
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import fr.acinq.eclair.transactions.Scripts.multiSig2of2
import fr.acinq.eclair.transactions.Transactions._
import fr.acinq.eclair.transactions.{Scripts, Transactions}
import fr.acinq.eclair.transactions.Transactions.{DefaultCommitmentFormat, InputInfo, PlaceHolderPubKey, PlaceHolderSig, TxOwner}
import scodec.bits.ByteVector

import scala.concurrent.Future
Expand All @@ -29,7 +29,7 @@ trait SpendFromChannelAddress {
Right(pubKeyScript) = addressToPublicKeyScript(appKit.nodeParams.chainHash, address).map(Script.write)
// build the tx a first time with a zero amount to compute the weight
fee = Transactions.weight2fee(feerate, buildTx(outPoint, 0.sat, pubKeyScript, dummy2of2Witness).weight())
_ = assert(inputAmount - fee > Transactions.dustLimit(pubKeyScript), s"amount insufficient (fee=$fee)")
_ = assert(inputAmount - fee > Scripts.dustLimit(pubKeyScript), s"amount insufficient (fee=$fee)")
unsignedTx = buildTx(outPoint, inputAmount - fee, pubKeyScript, dummy2of2Witness)
// the following are not used, but need to be sent to the counterparty
localFundingPubkey = appKit.nodeParams.channelKeyManager.fundingPublicKey(fundingKeyPath, fundingTxIndex).publicKey
Expand Down
20 changes: 9 additions & 11 deletions eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,7 @@ object Helpers {
}
}
}

}

object Closing {
Expand Down Expand Up @@ -816,7 +817,7 @@ object Helpers {
* The various dust limits are detailed in https://github.com/lightningnetwork/lightning-rfc/blob/master/03-transactions.md#dust-limits
*/
def checkClosingDustAmounts(closingTx: ClosingTx): Boolean = {
closingTx.tx.txOut.forall(txOut => txOut.amount >= Transactions.dustLimit(txOut.publicKeyScript))
closingTx.tx.txOut.forall(txOut => txOut.amount >= Scripts.dustLimit(txOut.publicKeyScript))
}
}

Expand Down Expand Up @@ -850,7 +851,7 @@ object Helpers {
*/
private def shouldUpdateAnchorTxs(anchorTxs: List[ClaimAnchorOutputTx], confirmationTarget: ConfirmationTarget): Boolean = {
anchorTxs
.collect { case tx: ClaimLocalAnchorOutputTx => tx.confirmationTarget }
.collect { case tx: ClaimAnchorOutputTx => tx.confirmationTarget }
.forall {
case ConfirmationTarget.Absolute(current) => confirmationTarget match {
case ConfirmationTarget.Absolute(proposed) => proposed < current
Expand Down Expand Up @@ -912,11 +913,8 @@ object Helpers {
val localFundingPubKey = keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath, commitment.fundingTxIndex).publicKey
val claimAnchorTxs = List(
withTxGenerationLog("local-anchor") {
Transactions.makeClaimLocalAnchorOutputTx(lcp.commitTx, localFundingPubKey, confirmationTarget)
Transactions.makeClaimAnchorOutputTx(lcp.commitTx, localFundingPubKey, confirmationTarget)
},
withTxGenerationLog("remote-anchor") {
Transactions.makeClaimRemoteAnchorOutputTx(lcp.commitTx, commitment.remoteFundingPubKey)
}
).flatten
lcp.copy(claimAnchorTxs = claimAnchorTxs)
} else {
Expand Down Expand Up @@ -1001,6 +999,7 @@ object Helpers {
(localCommitPublished, None)
}
}

}

object RemoteClose {
Expand Down Expand Up @@ -1040,12 +1039,9 @@ object Helpers {
if (shouldUpdateAnchorTxs(rcp.claimAnchorTxs, confirmationTarget)) {
val localFundingPubkey = keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath, commitment.fundingTxIndex).publicKey
val claimAnchorTxs = List(
withTxGenerationLog("local-anchor") {
Transactions.makeClaimLocalAnchorOutputTx(rcp.commitTx, localFundingPubkey, confirmationTarget)
},
withTxGenerationLog("remote-anchor") {
Transactions.makeClaimRemoteAnchorOutputTx(rcp.commitTx, commitment.remoteFundingPubKey)
}
Transactions.makeClaimAnchorOutputTx(rcp.commitTx, localFundingPubkey, confirmationTarget)
},
).flatten
rcp.copy(claimAnchorTxs = claimAnchorTxs)
} else {
Expand Down Expand Up @@ -1158,6 +1154,7 @@ object Helpers {
})
}.toSeq.flatten.flatten.toMap
}

}

object RevokedClose {
Expand Down Expand Up @@ -1328,6 +1325,7 @@ object Helpers {
(revokedCommitPublished, Nil)
}
}

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2169,15 +2169,15 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
val nrcp1 = d.nextRemoteCommitPublished.map(nrcp => Closing.RemoteClose.claimAnchors(keyManager, d.commitments.latest, nrcp, c.confirmationTarget))
// We favor the remote commitment(s) because they're more interesting than the local commitment (no CSV delays).
if (rcp1.nonEmpty) {
rcp1.foreach(rcp => rcp.claimAnchorTxs.collect { case tx: Transactions.ClaimLocalAnchorOutputTx => txPublisher ! PublishReplaceableTx(tx, d.commitments.latest, rcp.commitTx) })
rcp1.foreach(rcp => rcp.claimAnchorTxs.foreach { tx => txPublisher ! PublishReplaceableTx(tx, d.commitments.latest, rcp.commitTx) })
c.replyTo ! RES_SUCCESS(c, d.channelId)
stay() using d.copy(remoteCommitPublished = rcp1) storing()
} else if (nrcp1.nonEmpty) {
nrcp1.foreach(rcp => rcp.claimAnchorTxs.collect { case tx: Transactions.ClaimLocalAnchorOutputTx => txPublisher ! PublishReplaceableTx(tx, d.commitments.latest, rcp.commitTx) })
nrcp1.foreach(rcp => rcp.claimAnchorTxs.foreach { tx => txPublisher ! PublishReplaceableTx(tx, d.commitments.latest, rcp.commitTx) })
c.replyTo ! RES_SUCCESS(c, d.channelId)
stay() using d.copy(nextRemoteCommitPublished = nrcp1) storing()
} else if (lcp1.nonEmpty) {
lcp1.foreach(lcp => lcp.claimAnchorTxs.collect { case tx: Transactions.ClaimLocalAnchorOutputTx => txPublisher ! PublishReplaceableTx(tx, d.commitments.latest, lcp.commitTx) })
lcp1.foreach(lcp => lcp.claimAnchorTxs.foreach { tx => txPublisher ! PublishReplaceableTx(tx, d.commitments.latest, lcp.commitTx) })
c.replyTo ! RES_SUCCESS(c, d.channelId)
stay() using d.copy(localCommitPublished = lcp1) storing()
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ trait ErrorHandlers extends CommonHandlers {
List(PublishFinalTx(commitTx, commitment.commitInput.outPoint, commitment.capacity, "commit-tx", Closing.commitTxFee(commitment.commitInput, commitTx, localPaysCommitTxFees), None)) ++ (claimMainDelayedOutputTx.map(tx => PublishFinalTx(tx, tx.fee, None)) ++ redeemableHtlcTxs ++ claimHtlcDelayedTxs.map(tx => PublishFinalTx(tx, tx.fee, None)))
case _: Transactions.AnchorOutputsCommitmentFormat =>
val redeemableHtlcTxs = htlcTxs.values.flatten.map(tx => PublishReplaceableTx(tx, commitment, commitTx))
val claimLocalAnchor = claimAnchorTxs.collect { case tx: Transactions.ClaimLocalAnchorOutputTx if !localCommitPublished.isConfirmed => PublishReplaceableTx(tx, commitment, commitTx) }
val claimLocalAnchor = claimAnchorTxs.collect { case tx: Transactions.ClaimAnchorOutputTx if !localCommitPublished.isConfirmed => PublishReplaceableTx(tx, commitment, commitTx) }
List(PublishFinalTx(commitTx, commitment.commitInput.outPoint, commitment.capacity, "commit-tx", Closing.commitTxFee(commitment.commitInput, commitTx, localPaysCommitTxFees), None)) ++ claimLocalAnchor ++ claimMainDelayedOutputTx.map(tx => PublishFinalTx(tx, tx.fee, None)) ++ redeemableHtlcTxs ++ claimHtlcDelayedTxs.map(tx => PublishFinalTx(tx, tx.fee, None))
}
publishIfNeeded(publishQueue, irrevocablySpent)
Expand All @@ -251,7 +251,7 @@ trait ErrorHandlers extends CommonHandlers {
// We watch outputs of the commitment tx that both parties may spend.
// We also watch our local anchor: this ensures that we will correctly detect when it's confirmed and count its fees
// in the audit DB, even if we restart before confirmation.
val watchSpentQueue = htlcTxs.keys ++ claimAnchorTxs.collect { case tx: Transactions.ClaimLocalAnchorOutputTx if !localCommitPublished.isConfirmed => tx.input.outPoint }
val watchSpentQueue = htlcTxs.keys ++ claimAnchorTxs.collect { case tx: Transactions.ClaimAnchorOutputTx if !localCommitPublished.isConfirmed => tx.input.outPoint }
watchSpentIfNeeded(commitTx, watchSpentQueue, irrevocablySpent)
}

Expand Down Expand Up @@ -294,7 +294,7 @@ trait ErrorHandlers extends CommonHandlers {
def doPublish(remoteCommitPublished: RemoteCommitPublished, commitment: FullCommitment): Unit = {
import remoteCommitPublished._

val claimLocalAnchor = claimAnchorTxs.collect { case tx: Transactions.ClaimLocalAnchorOutputTx if !remoteCommitPublished.isConfirmed => PublishReplaceableTx(tx, commitment, commitTx) }
val claimLocalAnchor = claimAnchorTxs.collect { case tx: Transactions.ClaimAnchorOutputTx if !remoteCommitPublished.isConfirmed => PublishReplaceableTx(tx, commitment, commitTx) }
val redeemableHtlcTxs = claimHtlcTxs.values.flatten.map(tx => PublishReplaceableTx(tx, commitment, commitTx))
val publishQueue = claimLocalAnchor ++ claimMainOutputTx.map(tx => PublishFinalTx(tx, tx.fee, None)).toSeq ++ redeemableHtlcTxs
publishIfNeeded(publishQueue, irrevocablySpent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ object ReplaceableTxFunder {
val maxFee = txInfo match {
case tx: HtlcTx => tx.input.txOut.amount
case tx: ClaimHtlcTx => tx.input.txOut.amount
case _: ClaimLocalAnchorOutputTx =>
case _: ClaimAnchorOutputTx =>
val htlcBalance = commitment.localCommit.htlcTxsAndRemoteSigs.map(_.htlcTx.input.txOut.amount).sum
val mainBalance = commitment.localCommit.spec.toLocal.truncateToSatoshi
// If there are no HTLCs or a low HTLC amount, we still want to get back our main balance.
Expand All @@ -110,7 +110,7 @@ object ReplaceableTxFunder {
case _: ClaimHtlcSuccessTx => Transactions.claimHtlcSuccessWeight
case _: LegacyClaimHtlcSuccessTx => Transactions.claimHtlcSuccessWeight
case _: ClaimHtlcTimeoutTx => Transactions.claimHtlcTimeoutWeight
case _: ClaimLocalAnchorOutputTx => commitTx.weight() + Transactions.claimAnchorOutputMinWeight
case _: ClaimAnchorOutputTx => commitTx.weight() + Transactions.claimAnchorOutputMinWeight
}
// It doesn't make sense to use a feerate that is much higher than the current feerate for inclusion into the next block.
Transactions.fee2rate(maxFee, weight).min(currentFeerates.fastest * 1.25)
Expand Down Expand Up @@ -158,15 +158,15 @@ object ReplaceableTxFunder {
def adjustPreviousTxOutput(previousTx: FundedTx, targetFeerate: FeeratePerKw, commitment: FullCommitment, commitTx: Transaction): AdjustPreviousTxOutputResult = {
val dustLimit = commitment.localParams.dustLimit
val targetFee = previousTx.signedTxWithWitnessData match {
case _: ClaimLocalAnchorWithWitnessData =>
case _: ClaimAnchorWithWitnessData =>
val commitFee = commitment.localCommit.commitTxAndRemoteSig.commitTx.fee
val totalWeight = previousTx.signedTx.weight() + commitTx.weight()
weight2fee(targetFeerate, totalWeight) - commitFee
case _ =>
weight2fee(targetFeerate, previousTx.signedTx.weight())
}
previousTx.signedTxWithWitnessData match {
case claimLocalAnchor: ClaimLocalAnchorWithWitnessData =>
case claimLocalAnchor: ClaimAnchorWithWitnessData =>
val changeAmount = previousTx.totalAmountIn - targetFee
if (changeAmount < dustLimit) {
AdjustPreviousTxOutputResult.AddWalletInputs(claimLocalAnchor)
Expand Down Expand Up @@ -233,7 +233,7 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
def fund(txWithWitnessData: ReplaceableTxWithWitnessData, targetFeerate: FeeratePerKw): Behavior[Command] = {
log.info("funding {} tx (targetFeerate={})", txWithWitnessData.txInfo.desc, targetFeerate)
txWithWitnessData match {
case claimLocalAnchor: ClaimLocalAnchorWithWitnessData =>
case claimLocalAnchor: ClaimAnchorWithWitnessData =>
val commitFeerate = cmd.commitment.localCommit.spec.commitTxFeerate
if (targetFeerate <= commitFeerate) {
log.info("skipping {}: commit feerate is high enough (feerate={})", cmd.desc, commitFeerate)
Expand Down Expand Up @@ -312,7 +312,7 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
private def sign(fundedTx: ReplaceableTxWithWitnessData, txFeerate: FeeratePerKw, amountIn: Satoshi, walletUtxos: Map[OutPoint, TxOut]): Behavior[Command] = {
val channelKeyPath = keyManager.keyPath(cmd.commitment.localParams, cmd.commitment.params.channelConfig)
fundedTx match {
case claimAnchorTx: ClaimLocalAnchorWithWitnessData =>
case claimAnchorTx: ClaimAnchorWithWitnessData =>
val localSig = keyManager.sign(claimAnchorTx.txInfo, keyManager.fundingPublicKey(cmd.commitment.localParams.fundingKeyPath, cmd.commitment.fundingTxIndex), TxOwner.Local, cmd.commitment.params.commitmentFormat, walletUtxos)
val signedTx = claimAnchorTx.copy(txInfo = addSigs(claimAnchorTx.txInfo, localSig))
signWalletInputs(signedTx, txFeerate, amountIn, walletUtxos)
Expand Down Expand Up @@ -378,7 +378,7 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
// For "claim anchor txs" there is a single change output that sends to our on-chain wallet.
// For htlc txs the first output is the one we want to fund/bump, additional outputs send to our on-chain wallet.
val ourWalletOutputs = locallySignedTx match {
case _: ClaimLocalAnchorWithWitnessData => Seq(0)
case _: ClaimAnchorWithWitnessData => Seq(0)
case _: HtlcWithWitnessData => locallySignedTx.txInfo.tx.txOut.indices.tail
}
context.pipeToSelf(bitcoinClient.signPsbt(psbt1, ourWalletInputs, ourWalletOutputs)) {
Expand All @@ -387,7 +387,7 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
case Right(signedTx) =>
val actualFees = kmp2scala(processPsbtResponse.psbt.computeFees())
val actualWeight = locallySignedTx match {
case _: ClaimLocalAnchorWithWitnessData => signedTx.weight() + cmd.commitTx.weight()
case _: ClaimAnchorWithWitnessData => signedTx.weight() + cmd.commitTx.weight()
case _ => signedTx.weight()
}
val actualFeerate = Transactions.fee2rate(actualFees, actualWeight)
Expand Down Expand Up @@ -438,14 +438,14 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
private def addInputs(tx: ReplaceableTxWithWalletInputs, targetFeerate: FeeratePerKw, commitment: FullCommitment): Future[(ReplaceableTxWithWalletInputs, Satoshi, Map[OutPoint, TxOut])] = {
for {
(fundedTx, amountIn) <- tx match {
case anchorTx: ClaimLocalAnchorWithWitnessData => addInputs(anchorTx, targetFeerate, commitment)
case anchorTx: ClaimAnchorWithWitnessData => addInputs(anchorTx, targetFeerate, commitment)
case htlcTx: HtlcWithWitnessData => addInputs(htlcTx, targetFeerate, commitment)
}
spentUtxos <- getWalletUtxos(fundedTx.txInfo)
} yield (fundedTx, amountIn, spentUtxos)
}

private def addInputs(anchorTx: ClaimLocalAnchorWithWitnessData, targetFeerate: FeeratePerKw, commitment: FullCommitment): Future[(ClaimLocalAnchorWithWitnessData, Satoshi)] = {
private def addInputs(anchorTx: ClaimAnchorWithWitnessData, targetFeerate: FeeratePerKw, commitment: FullCommitment): Future[(ClaimAnchorWithWitnessData, Satoshi)] = {
// We want to pay the commit fees using CPFP. Since the commit tx may not be in the mempool yet (its feerate may be
// below the minimum acceptable mempool feerate), we cannot ask bitcoind to fund a transaction that spends that
// commit tx: it would fail because it cannot find the input in the utxo set. So we instead ask bitcoind to fund an
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ object ReplaceableTxPrePublisher {
sealed trait ReplaceableTxWithWalletInputs extends ReplaceableTxWithWitnessData {
override def updateTx(tx: Transaction): ReplaceableTxWithWalletInputs
}
case class ClaimLocalAnchorWithWitnessData(txInfo: ClaimLocalAnchorOutputTx) extends ReplaceableTxWithWalletInputs {
override def updateTx(tx: Transaction): ClaimLocalAnchorWithWitnessData = this.copy(txInfo = this.txInfo.copy(tx = tx))
case class ClaimAnchorWithWitnessData(txInfo: ClaimAnchorOutputTx) extends ReplaceableTxWithWalletInputs {
override def updateTx(tx: Transaction): ClaimAnchorWithWitnessData = this.copy(txInfo = this.txInfo.copy(tx = tx))
}
sealed trait HtlcWithWitnessData extends ReplaceableTxWithWalletInputs {
override def txInfo: HtlcTx
Expand Down Expand Up @@ -105,7 +105,7 @@ object ReplaceableTxPrePublisher {
case CheckPreconditions(replyTo, cmd) =>
val prePublisher = new ReplaceableTxPrePublisher(nodeParams, replyTo, cmd, bitcoinClient, context)
cmd.txInfo match {
case localAnchorTx: Transactions.ClaimLocalAnchorOutputTx => prePublisher.checkAnchorPreconditions(localAnchorTx)
case localAnchorTx: Transactions.ClaimAnchorOutputTx => prePublisher.checkAnchorPreconditions(localAnchorTx)
case htlcTx: Transactions.HtlcTx => prePublisher.checkHtlcPreconditions(htlcTx)
case claimHtlcTx: Transactions.ClaimHtlcTx => prePublisher.checkClaimHtlcPreconditions(claimHtlcTx)
}
Expand All @@ -126,7 +126,7 @@ private class ReplaceableTxPrePublisher(nodeParams: NodeParams,

private val log = context.log

private def checkAnchorPreconditions(localAnchorTx: ClaimLocalAnchorOutputTx): Behavior[Command] = {
private def checkAnchorPreconditions(localAnchorTx: ClaimAnchorOutputTx): Behavior[Command] = {
// We verify that:
// - our commit is not confirmed (if it is, no need to claim our anchor)
// - their commit is not confirmed (if it is, no need to claim our anchor either)
Expand Down Expand Up @@ -162,7 +162,7 @@ private class ReplaceableTxPrePublisher(nodeParams: NodeParams,
}
Behaviors.receiveMessagePartial {
case ParentTxOk =>
replyTo ! PreconditionsOk(ClaimLocalAnchorWithWitnessData(localAnchorTx))
replyTo ! PreconditionsOk(ClaimAnchorWithWitnessData(localAnchorTx))
Behaviors.stopped
case FundingTxNotFound =>
log.debug("funding tx could not be found, we don't know yet if we need to claim our anchor")
Expand All @@ -179,7 +179,7 @@ private class ReplaceableTxPrePublisher(nodeParams: NodeParams,
case UnknownFailure(reason) =>
log.error(s"could not check ${cmd.desc} preconditions, proceeding anyway: ", reason)
// If our checks fail, we don't want it to prevent us from trying to publish our commit tx.
replyTo ! PreconditionsOk(ClaimLocalAnchorWithWitnessData(localAnchorTx))
replyTo ! PreconditionsOk(ClaimAnchorWithWitnessData(localAnchorTx))
Behaviors.stopped
}
}
Expand Down
Loading