Skip to content

Commit

Permalink
Simplify ReplaceableTxFunder
Browse files Browse the repository at this point in the history
  • Loading branch information
sstone committed Aug 2, 2023
1 parent c5bb39f commit f9fa8a7
Showing 1 changed file with 27 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ object ReplaceableTxFunder {
sealed trait Command
case class FundTransaction(replyTo: ActorRef[FundingResult], cmd: TxPublisher.PublishReplaceableTx, tx: Either[FundedTx, ReplaceableTxWithWitnessData], targetFeerate: FeeratePerKw) extends Command

private case class AddInputsOk(tx: ReplaceableTxWithWitnessData, totalAmountIn: Satoshi, packageWeight: Int) extends Command
private case class AddInputsOk(tx: ReplaceableTxWithWitnessData, totalAmountIn: Satoshi) extends Command
private case class AddInputsFailed(reason: Throwable) extends Command
private case class SignWalletInputsOk(signedTx: Transaction) extends Command
private case class SignWalletInputsFailed(reason: Throwable) extends Command
Expand Down Expand Up @@ -254,7 +254,7 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
val htlcFeerate = cmd.commitment.localCommit.spec.htlcTxFeerate(cmd.commitment.params.commitmentFormat)
if (targetFeerate <= htlcFeerate) {
log.info("publishing {} without adding inputs: txid={}", cmd.desc, htlcTx.txInfo.tx.txid)
sign(txWithWitnessData, htlcFeerate, htlcTx.txInfo.amountIn, htlcTx.txInfo.tx.weight())
sign(txWithWitnessData, htlcFeerate, htlcTx.txInfo.amountIn)
} else {
addWalletInputs(htlcTx, targetFeerate)
}
Expand All @@ -266,7 +266,7 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
replyTo ! FundingFailed(TxPublisher.TxRejectedReason.TxSkipped(retryNextBlock = true))
Behaviors.stopped
case Right(updatedClaimHtlcTx) =>
sign(updatedClaimHtlcTx, targetFeerate, updatedClaimHtlcTx.txInfo.amountIn, updatedClaimHtlcTx.txInfo.tx.weight())
sign(updatedClaimHtlcTx, targetFeerate, updatedClaimHtlcTx.txInfo.amountIn)
}
}
}
Expand All @@ -280,7 +280,7 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
Behaviors.stopped
case AdjustPreviousTxOutputResult.TxOutputAdjusted(updatedTx) =>
log.debug("bumping {} fees without adding new inputs: txid={}", cmd.desc, updatedTx.txInfo.tx.txid)
sign(updatedTx, targetFeerate, previousTx.totalAmountIn, updatedTx.txInfo.tx.weight())
sign(updatedTx, targetFeerate, previousTx.totalAmountIn)
case AdjustPreviousTxOutputResult.AddWalletInputs(tx) =>
log.debug("bumping {} fees requires adding new inputs (feerate={})", cmd.desc, targetFeerate)
// We restore the original transaction (remove previous attempt's wallet inputs).
Expand All @@ -291,13 +291,13 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,

private def addWalletInputs(txWithWitnessData: ReplaceableTxWithWalletInputs, targetFeerate: FeeratePerKw): Behavior[Command] = {
context.pipeToSelf(addInputs(txWithWitnessData, targetFeerate, cmd.commitment)) {
case Success((fundedTx, totalAmountIn, packageWeight)) => AddInputsOk(fundedTx, totalAmountIn, packageWeight)
case Success((fundedTx, totalAmountIn)) => AddInputsOk(fundedTx, totalAmountIn)
case Failure(reason) => AddInputsFailed(reason)
}
Behaviors.receiveMessagePartial {
case AddInputsOk(fundedTx, totalAmountIn, packageWeight) =>
case AddInputsOk(fundedTx, totalAmountIn) =>
log.info("added {} wallet input(s) and {} wallet output(s) to {}", fundedTx.txInfo.tx.txIn.length - 1, fundedTx.txInfo.tx.txOut.length - 1, cmd.desc)
sign(fundedTx, targetFeerate, totalAmountIn, packageWeight)
sign(fundedTx, targetFeerate, totalAmountIn)
case AddInputsFailed(reason) =>
if (reason.getMessage.contains("Insufficient funds")) {
val nodeOperatorMessage =
Expand All @@ -315,13 +315,13 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
}
}

private def sign(fundedTx: ReplaceableTxWithWitnessData, txFeerate: FeeratePerKw, amountIn: Satoshi, packageWeight: Int): Behavior[Command] = {
private def sign(fundedTx: ReplaceableTxWithWitnessData, txFeerate: FeeratePerKw, amountIn: Satoshi): Behavior[Command] = {
val channelKeyPath = keyManager.keyPath(cmd.commitment.localParams, cmd.commitment.params.channelConfig)
fundedTx match {
case claimAnchorTx: ClaimLocalAnchorWithWitnessData =>
val localSig = keyManager.sign(claimAnchorTx.txInfo, keyManager.fundingPublicKey(cmd.commitment.localParams.fundingKeyPath, cmd.commitment.fundingTxIndex), TxOwner.Local, cmd.commitment.params.commitmentFormat)
val signedTx = claimAnchorTx.copy(txInfo = addSigs(claimAnchorTx.txInfo, localSig))
signWalletInputs(signedTx, txFeerate, amountIn, packageWeight)
signWalletInputs(signedTx, txFeerate, amountIn)
case htlcTx: HtlcWithWitnessData =>
val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, cmd.commitment.localCommit.index)
val localHtlcBasepoint = keyManager.htlcPoint(channelKeyPath)
Expand All @@ -332,7 +332,7 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
}
val hasWalletInputs = htlcTx.txInfo.tx.txIn.size > 1
if (hasWalletInputs) {
signWalletInputs(signedTx, txFeerate, amountIn, packageWeight)
signWalletInputs(signedTx, txFeerate, amountIn)
} else {
replyTo ! TransactionReady(FundedTx(signedTx, amountIn, txFeerate))
Behaviors.stopped
Expand All @@ -353,7 +353,7 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
}
}

private def signWalletInputs(locallySignedTx: ReplaceableTxWithWalletInputs, txFeerate: FeeratePerKw, amountIn: Satoshi, packageWeight: Int): Behavior[Command] = {
private def signWalletInputs(locallySignedTx: ReplaceableTxWithWalletInputs, txFeerate: FeeratePerKw, amountIn: Satoshi): Behavior[Command] = {
import fr.acinq.bitcoin.scalacompat.KotlinUtils._

// we finalize (sign) the input that we control, and will then ask our bitcoin client to sign wallet inputs
Expand All @@ -371,7 +371,17 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
case Success(processPsbtResponse) =>
val signedTx = processPsbtResponse.finalTx
val actualFees = kmp2scala(processPsbtResponse.psbt.computeFees())
val actualFeerate = FeeratePerKw((actualFees * 1000) / packageWeight)
val actualWeight = locallySignedTx match {
case _: ClaimLocalAnchorWithWitnessData => signedTx.weight() + dummySignedCommitTx(cmd.commitment).tx.weight()
case _ => {
locallySignedTx.txInfo match {
case _: HtlcSuccessTx => cmd.commitment.params.commitmentFormat.htlcSuccessInputWeight + signedTx.weight()
case _: HtlcTimeoutTx => cmd.commitment.params.commitmentFormat.htlcTimeoutInputWeight + signedTx.weight()
case _ => signedTx.weight()
}
}
}
val actualFeerate = FeeratePerKw((actualFees * 1000) / actualWeight)
if (actualFeerate >= txFeerate * 2) {
SignWalletInputsFailed(new RuntimeException(s"actual fee rate $actualFeerate is more than twice the requested fee rate $txFeerate"))
} else {
Expand Down Expand Up @@ -405,14 +415,14 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
}
}

private def addInputs(tx: ReplaceableTxWithWalletInputs, targetFeerate: FeeratePerKw, commitment: FullCommitment): Future[(ReplaceableTxWithWalletInputs, Satoshi, Int)] = {
private def addInputs(tx: ReplaceableTxWithWalletInputs, targetFeerate: FeeratePerKw, commitment: FullCommitment): Future[(ReplaceableTxWithWalletInputs, Satoshi)] = {
tx match {
case anchorTx: ClaimLocalAnchorWithWitnessData => addInputs(anchorTx, targetFeerate, commitment)
case htlcTx: HtlcWithWitnessData => addInputs(htlcTx, targetFeerate, commitment)
}
}

private def addInputs(anchorTx: ClaimLocalAnchorWithWitnessData, targetFeerate: FeeratePerKw, commitment: FullCommitment): Future[(ClaimLocalAnchorWithWitnessData, Satoshi, Int)] = {
private def addInputs(anchorTx: ClaimLocalAnchorWithWitnessData, targetFeerate: FeeratePerKw, commitment: FullCommitment): Future[(ClaimLocalAnchorWithWitnessData, Satoshi)] = {
import fr.acinq.bitcoin.scalacompat.KotlinUtils._

val dustLimit = commitment.localParams.dustLimit
Expand Down Expand Up @@ -452,7 +462,6 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
partiallySignedTx = processPsbtResponse.extractPartiallySignedTx
dummySignedTx = addSigs(anchorTx.updateTx(partiallySignedTx).txInfo, PlaceHolderSig)
packageWeight = commitTx.weight() + dummySignedTx.tx.weight()

// above, we asked bitcoin core to use the package weight to estimate fees when it built and funded this transaction, so we
// use the same package weight here to compute the actual fee rate that we get
actualFeerate = FeeratePerKw((processPsbtResponse.psbt.computeFees() * 1000) / packageWeight)
Expand All @@ -462,11 +471,11 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
changeAmount = dustLimit.max(fundTxResponse.amountIn - anchorTxFee)
fundedTx = fundTxResponse.tx.copy(txOut = Seq(changeOutput.copy(amount = changeAmount)))
} yield {
(anchorTx.updateTx(fundedTx).updateWalletInputsAndOutputs(ourWalletInputs, ourWalletOutputs), fundTxResponse.amountIn, packageWeight)
(anchorTx.updateTx(fundedTx).updateWalletInputsAndOutputs(ourWalletInputs, ourWalletOutputs), fundTxResponse.amountIn)
}
}

private def addInputs(htlcTx: HtlcWithWitnessData, targetFeerate: FeeratePerKw, commitment: FullCommitment): Future[(HtlcWithWitnessData, Satoshi, Int)] = {
private def addInputs(htlcTx: HtlcWithWitnessData, targetFeerate: FeeratePerKw, commitment: FullCommitment): Future[(HtlcWithWitnessData, Satoshi)] = {
import fr.acinq.bitcoin.scalacompat.KotlinUtils._

val htlcInputWeight = InputWeight(htlcTx.txInfo.input.outPoint, htlcTx.txInfo match {
Expand All @@ -485,7 +494,7 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
val actualFeerate = FeeratePerKw((fundTxResponse.fee * 1000) / packageWeight)
require(actualFeerate < targetFeerate * 2, s"actual fee rate $actualFeerate is more than twice the requested fee rate $targetFeerate")

(unsignedTx, fundTxResponse.amountIn, packageWeight.toInt)
(unsignedTx, fundTxResponse.amountIn)
})
})
}
Expand Down

0 comments on commit f9fa8a7

Please sign in to comment.