Skip to content
Closed
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 @@ -134,7 +134,7 @@ case class NodeParams(nodeKeyManager: NodeKeyManager,
min = (commitmentFeerate * feerateTolerance.ratioLow).max(minimumFeerate),
max = (commitmentFormat match {
case Transactions.DefaultCommitmentFormat => commitmentFeerate * feerateTolerance.ratioHigh
case _: Transactions.AnchorOutputsCommitmentFormat => (commitmentFeerate * feerateTolerance.ratioHigh).max(feerateTolerance.anchorOutputMaxCommitFeerate)
case _: Transactions.AnchorOutputsCommitmentFormat | Transactions.SimpleTaprootChannelCommitmentFormat => (commitmentFeerate * feerateTolerance.ratioHigh).max(feerateTolerance.anchorOutputMaxCommitFeerate)
}).max(minimumFeerate),
)
RecommendedFeerates(chainHash, fundingFeerate, commitmentFeerate, TlvStream(fundingRange, commitmentRange))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ package fr.acinq.eclair
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.channel.ChannelConfig
import fr.acinq.eclair.transactions.Scripts.multiSig2of2
import fr.acinq.eclair.channel.{ChannelConfig, ChannelSpendSignature}
import fr.acinq.eclair.transactions.Transactions._
import fr.acinq.eclair.transactions.{Scripts, Transactions}
import scodec.bits.ByteVector
Expand All @@ -15,9 +14,6 @@ trait SpendFromChannelAddress {

this: EclairImpl =>

/** these dummy witnesses are used as a placeholder to accurately compute the weight */
private val dummy2of2Witness = Scripts.witness2of2(PlaceHolderSig, PlaceHolderSig, PlaceHolderPubKey, PlaceHolderPubKey)

private def buildTx(outPoint: OutPoint, outputAmount: Satoshi, pubKeyScript: ByteVector, witness: ScriptWitness) = Transaction(2,
txIn = Seq(TxIn(outPoint, ByteVector.empty, 0, witness)),
txOut = Seq(TxOut(outputAmount, pubKeyScript)),
Expand All @@ -28,13 +24,13 @@ trait SpendFromChannelAddress {
inputTx <- appKit.wallet.getTransaction(outPoint.txid)
inputAmount = inputTx.txOut(outPoint.index.toInt).amount
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 > 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
channelKeys = appKit.nodeParams.channelKeyManager.channelKeys(ChannelConfig.standard, fundingKeyPath)
localFundingPubkey = channelKeys.fundingKey(fundingTxIndex).publicKey
// build the tx a first time with a zero amount to compute the weight
dummyWitness = Scripts.witness2of2(PlaceHolderSig, PlaceHolderSig, localFundingPubkey, localFundingPubkey)
fee = Transactions.weight2fee(feerate, buildTx(outPoint, 0.sat, pubKeyScript, dummyWitness).weight())
_ = assert(inputAmount - fee > Scripts.dustLimit(pubKeyScript), s"amount insufficient (fee=$fee)")
unsignedTx = buildTx(outPoint, inputAmount - fee, pubKeyScript, dummyWitness)
} yield SpendFromChannelPrep(fundingTxIndex, localFundingPubkey, inputAmount, unsignedTx)
}

Expand All @@ -45,12 +41,11 @@ trait SpendFromChannelAddress {
inputTx <- appKit.wallet.getTransaction(outPoint.txid)
channelKeys = appKit.nodeParams.channelKeyManager.channelKeys(ChannelConfig.standard, fundingKeyPath)
localFundingKey = channelKeys.fundingKey(fundingTxIndex)
fundingRedeemScript = multiSig2of2(localFundingKey.publicKey, remoteFundingPubkey)
inputInfo = InputInfo(outPoint, inputTx.txOut(outPoint.index.toInt), fundingRedeemScript)
inputInfo = InputInfo(outPoint, inputTx.txOut(outPoint.index.toInt), ByteVector.empty)
// classify as splice, doesn't really matter
localSig = Transactions.SpliceTx(inputInfo, unsignedTx).sign(localFundingKey, TxOwner.Local, DefaultCommitmentFormat, Map.empty)
witness = Scripts.witness2of2(localSig, remoteSig, localFundingKey.publicKey, remoteFundingPubkey)
signedTx = unsignedTx.updateWitness(0, witness)
tx = Transactions.SpliceTx(inputInfo, unsignedTx)
localSig = tx.sign(localFundingKey, remoteFundingPubkey, extraUtxos = Map.empty)
signedTx = tx.aggregateSigs(localFundingKey.publicKey, remoteFundingPubkey, localSig, ChannelSpendSignature.IndividualSignature(remoteSig))
} yield SpendFromChannelResult(signedTx)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,19 +210,16 @@ object CheckBalance {
// be pruned and we will count twice the amount in the global (onChain + offChain) balance, until the
// mutual close tx gets deeply confirmed and the channel is removed.
val mutualClose = d.mutualClosePublished.last
val outputInfo_opt = mutualClose.toLocalOutput match {
case Some(outputInfo) => Some(outputInfo)
val outputIndex_opt = mutualClose.toLocalOutputIndex_opt match {
case Some(outputIndex) => Some(outputIndex)
case None =>
// Normally this would mean that we don't actually have an output, but due to a migration
// the data might not be accurate, see [[ChannelTypes0.migrateClosingTx]]
// As a (hackish) workaround, we use the pubkeyscript to retrieve our output
Transactions.findPubKeyScriptIndex(mutualClose.tx, d.finalScriptPubKey) match {
case Right(outputIndex) => Some(OutputInfo(outputIndex, mutualClose.tx.txOut(outputIndex).amount, d.finalScriptPubKey))
case _ => None // either we don't have an output (below dust), or we have used a non-default pubkey script
}
Transactions.findPubKeyScriptIndex(mutualClose.tx, d.finalScriptPubKey).map(_.toLong).toOption
}
outputInfo_opt match {
case Some(outputInfo) => r.copy(closing = r.closing.copy(mutualCloseBalance = r.closing.mutualCloseBalance.copy(toLocal = r.closing.mutualCloseBalance.toLocal + (OutPoint(mutualClose.tx.txid, outputInfo.index) -> outputInfo.amount))))
outputIndex_opt match {
case Some(index) => r.copy(closing = r.closing.copy(mutualCloseBalance = r.closing.mutualCloseBalance.copy(toLocal = r.closing.mutualCloseBalance.toLocal + (OutPoint(mutualClose.tx.txid, index) -> mutualClose.tx.txOut(index.toInt).amount))))
case None => r
}
case Some(localClose: LocalClose) => r.copy(closing = r.closing.copy(localCloseBalance = updatePossiblyPublishedBalance(r.closing.localCloseBalance, computeLocalCloseBalance(d.commitments.changes, localClose, d.commitments.originChannels, knownPreimages))))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ case class FeerateTolerance(ratioLow: Double, ratioHigh: Double, anchorOutputMax

def isProposedFeerateTooHigh(commitmentFormat: CommitmentFormat, networkFeerate: FeeratePerKw, proposedFeerate: FeeratePerKw): Boolean = {
commitmentFormat match {
case Transactions.DefaultCommitmentFormat => networkFeerate * ratioHigh < proposedFeerate
case Transactions.DefaultCommitmentFormat | Transactions.SimpleTaprootChannelCommitmentFormat => networkFeerate * ratioHigh < proposedFeerate
case ZeroFeeHtlcTxAnchorOutputsCommitmentFormat | UnsafeLegacyAnchorOutputsCommitmentFormat => networkFeerate * ratioHigh < proposedFeerate
}
}
Expand All @@ -85,7 +85,7 @@ case class FeerateTolerance(ratioLow: Double, ratioHigh: Double, anchorOutputMax
commitmentFormat match {
case Transactions.DefaultCommitmentFormat => proposedFeerate < networkFeerate * ratioLow
// When using anchor outputs, we allow low feerates: fees will be set with CPFP and RBF at broadcast time.
case ZeroFeeHtlcTxAnchorOutputsCommitmentFormat | UnsafeLegacyAnchorOutputsCommitmentFormat => false
case ZeroFeeHtlcTxAnchorOutputsCommitmentFormat | UnsafeLegacyAnchorOutputsCommitmentFormat | Transactions.SimpleTaprootChannelCommitmentFormat => false
}
}
}
Expand Down Expand Up @@ -121,7 +121,7 @@ case class OnChainFeeConf(feeTargets: FeeTargets,

commitmentFormat match {
case Transactions.DefaultCommitmentFormat => networkFeerate
case _: Transactions.AnchorOutputsCommitmentFormat =>
case _: Transactions.AnchorOutputsCommitmentFormat | Transactions.SimpleTaprootChannelCommitmentFormat=>
val targetFeerate = networkFeerate.min(feerateToleranceFor(remoteNodeId).anchorOutputMaxCommitFeerate)
// We make sure the feerate is always greater than the propagation threshold.
targetFeerate.max(networkMinFee * 1.25)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package fr.acinq.eclair.channel
import akka.actor.{ActorRef, PossiblyHarmful, typed}
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
import fr.acinq.bitcoin.scalacompat.{ByteVector32, DeterministicWallet, OutPoint, Satoshi, SatoshiLong, Transaction, TxId, TxOut}
import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerKw}
import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerKw, OnChainFeeConf}
import fr.acinq.eclair.channel.LocalFundingStatus.DualFundedUnconfirmedFundingTx
import fr.acinq.eclair.channel.fund.InteractiveTxBuilder._
import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningSession}
Expand Down Expand Up @@ -313,7 +313,7 @@ sealed trait CommitPublished {
def commitTx: Transaction
/** Map of relevant outpoints that have been spent and the confirmed transaction that spends them. */
def irrevocablySpent: Map[OutPoint, Transaction]

/** Returns true if the commitment transaction is confirmed. */
def isConfirmed: Boolean = {
// NB: if multiple transactions end up in the same block, the first confirmation we receive may not be the commit tx.
// However if the confirmed tx spends from the commit tx, we know that the commit tx is already confirmed and we know
Expand All @@ -334,6 +334,20 @@ sealed trait CommitPublished {
* is economical to do so to avoid polluting the utxo set.
*/
case class LocalCommitPublished(commitTx: Transaction, claimMainDelayedOutputTx: Option[ClaimLocalDelayedOutputTx], htlcTxs: Map[OutPoint, Option[HtlcTx]], claimHtlcDelayedTxs: List[HtlcDelayedTx], claimAnchorTxs: List[ClaimAnchorOutputTx], irrevocablySpent: Map[OutPoint, Transaction]) extends CommitPublished {
/** Compute the confirmation target that should be used to get the [[commitTx]] confirmed. */
def confirmationTarget(onChainFeeConf: OnChainFeeConf): Option[ConfirmationTarget] = {
if (isConfirmed) {
None
} else {
htlcTxs.values.flatten.map(_.htlcExpiry.blockHeight).minOption match {
// If there are pending HTLCs, we must get the commit tx confirmed before they timeout.
case Some(htlcExpiry) => Some(ConfirmationTarget.Absolute(htlcExpiry))
// Otherwise, we don't have funds at risk, so we can aim for a slower confirmation.
case None => Some(ConfirmationTarget.Priority(onChainFeeConf.feeTargets.closing))
}
}
}

/**
* A local commit is considered done when:
* - all commitment tx outputs that we can spend have been spent and confirmed (even if the spending tx was not ours)
Expand Down Expand Up @@ -368,6 +382,20 @@ case class LocalCommitPublished(commitTx: Transaction, claimMainDelayedOutputTx:
* economical to do so to avoid polluting the utxo set.
*/
case class RemoteCommitPublished(commitTx: Transaction, claimMainOutputTx: Option[ClaimRemoteCommitMainOutputTx], claimHtlcTxs: Map[OutPoint, Option[ClaimHtlcTx]], claimAnchorTxs: List[ClaimAnchorOutputTx], irrevocablySpent: Map[OutPoint, Transaction]) extends CommitPublished {
/** Compute the confirmation target that should be used to get the [[commitTx]] confirmed. */
def confirmationTarget(onChainFeeConf: OnChainFeeConf): Option[ConfirmationTarget] = {
if (isConfirmed) {
None
} else {
claimHtlcTxs.values.flatten.map(_.htlcExpiry.blockHeight).minOption match {
// If there are pending HTLCs, we must get the commit tx confirmed before they timeout.
case Some(htlcExpiry) => Some(ConfirmationTarget.Absolute(htlcExpiry))
// Otherwise, we don't have funds at risk, so we can aim for a slower confirmation.
case None => Some(ConfirmationTarget.Priority(onChainFeeConf.feeTargets.closing))
}
}
}

/**
* A remote commit is considered done when all commitment tx outputs that we can spend have been spent and confirmed
* (even if the spending tx was not ours).
Expand Down
Loading