@@ -32,6 +32,7 @@ import fr.acinq.eclair.transactions.Transactions._
3232import fr .acinq .eclair .{NodeParams , NotificationsLogger }
3333
3434import scala .concurrent .{ExecutionContext , Future }
35+ import scala .jdk .CollectionConverters .ListHasAsScala
3536import scala .util .{Failure , Success }
3637
3738/**
@@ -49,7 +50,7 @@ object ReplaceableTxFunder {
4950 sealed trait Command
5051 case class FundTransaction (replyTo : ActorRef [FundingResult ], cmd : TxPublisher .PublishReplaceableTx , tx : Either [FundedTx , ReplaceableTxWithWitnessData ], targetFeerate : FeeratePerKw ) extends Command
5152
52- private case class AddInputsOk (tx : ReplaceableTxWithWitnessData , totalAmountIn : Satoshi ) extends Command
53+ private case class AddInputsOk (tx : ReplaceableTxWithWitnessData , totalAmountIn : Satoshi , spentUtxos : Seq [ TxOut ] ) extends Command
5354 private case class AddInputsFailed (reason : Throwable ) extends Command
5455 private case class SignWalletInputsOk (signedTx : Transaction ) extends Command
5556 private case class SignWalletInputsFailed (reason : Throwable ) extends Command
@@ -237,7 +238,7 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
237238 private val log = context.log
238239
239240 def fund (txWithWitnessData : ReplaceableTxWithWitnessData , targetFeerate : FeeratePerKw ): Behavior [Command ] = {
240- log.info(" funding {} tx (targetFeerate={})" , txWithWitnessData.txInfo.desc, targetFeerate)
241+ log.info(" funding {} tx (targetFeerate={}) txId={} " , txWithWitnessData.txInfo.desc, targetFeerate, txWithWitnessData.txInfo.tx.txid )
241242 txWithWitnessData match {
242243 case claimLocalAnchor : ClaimLocalAnchorWithWitnessData =>
243244 val commitFeerate = cmd.commitment.localCommit.spec.commitTxFeerate
@@ -254,7 +255,7 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
254255 val htlcFeerate = cmd.commitment.localCommit.spec.htlcTxFeerate(cmd.commitment.params.commitmentFormat)
255256 if (targetFeerate <= htlcFeerate) {
256257 log.debug(" publishing {} without adding inputs: txid={}" , cmd.desc, htlcTx.txInfo.tx.txid)
257- sign(txWithWitnessData, htlcFeerate, htlcTx.txInfo.amountIn)
258+ sign(txWithWitnessData, htlcFeerate, htlcTx.txInfo.amountIn, None )
258259 } else {
259260 addWalletInputs(htlcTx, targetFeerate)
260261 }
@@ -266,7 +267,7 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
266267 replyTo ! FundingFailed (TxPublisher .TxRejectedReason .TxSkipped (retryNextBlock = true ))
267268 Behaviors .stopped
268269 case Right (updatedClaimHtlcTx) =>
269- sign(updatedClaimHtlcTx, targetFeerate, updatedClaimHtlcTx.txInfo.amountIn)
270+ sign(updatedClaimHtlcTx, targetFeerate, updatedClaimHtlcTx.txInfo.amountIn, None )
270271 }
271272 }
272273 }
@@ -280,7 +281,7 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
280281 Behaviors .stopped
281282 case AdjustPreviousTxOutputResult .TxOutputAdjusted (updatedTx) =>
282283 log.debug(" bumping {} fees without adding new inputs: txid={}" , cmd.desc, updatedTx.txInfo.tx.txid)
283- sign(updatedTx, targetFeerate, previousTx.totalAmountIn)
284+ sign(updatedTx, targetFeerate, previousTx.totalAmountIn, None )
284285 case AdjustPreviousTxOutputResult .AddWalletInputs (tx) =>
285286 log.debug(" bumping {} fees requires adding new inputs (feerate={})" , cmd.desc, targetFeerate)
286287 // We restore the original transaction (remove previous attempt's wallet inputs).
@@ -291,13 +292,13 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
291292
292293 private def addWalletInputs (txWithWitnessData : ReplaceableTxWithWalletInputs , targetFeerate : FeeratePerKw ): Behavior [Command ] = {
293294 context.pipeToSelf(addInputs(txWithWitnessData, targetFeerate, cmd.commitment)) {
294- case Success ((fundedTx, totalAmountIn)) => AddInputsOk (fundedTx, totalAmountIn)
295+ case Success ((fundedTx, totalAmountIn, psbt )) => AddInputsOk (fundedTx, totalAmountIn, psbt )
295296 case Failure (reason) => AddInputsFailed (reason)
296297 }
297298 Behaviors .receiveMessagePartial {
298- case AddInputsOk (fundedTx, totalAmountIn) =>
299+ case AddInputsOk (fundedTx, totalAmountIn, spentUtxos ) =>
299300 log.debug(" added {} wallet input(s) and {} wallet output(s) to {}" , fundedTx.txInfo.tx.txIn.length - 1 , fundedTx.txInfo.tx.txOut.length - 1 , cmd.desc)
300- sign(fundedTx, targetFeerate, totalAmountIn)
301+ sign(fundedTx, targetFeerate, totalAmountIn, Some (spentUtxos) )
301302 case AddInputsFailed (reason) =>
302303 if (reason.getMessage.contains(" Insufficient funds" )) {
303304 val nodeOperatorMessage =
@@ -315,17 +316,33 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
315316 }
316317 }
317318
318- private def sign (fundedTx : ReplaceableTxWithWitnessData , txFeerate : FeeratePerKw , amountIn : Satoshi ): Behavior [Command ] = {
319+ private def sign (fundedTx : ReplaceableTxWithWitnessData , txFeerate : FeeratePerKw , amountIn : Satoshi , spentUtxos : Option [ Seq [ TxOut ]] ): Behavior [Command ] = {
319320 val channelKeyPath = keyManager.keyPath(cmd.commitment.localParams, cmd.commitment.params.channelConfig)
320321 fundedTx match {
321322 case claimAnchorTx : ClaimLocalAnchorWithWitnessData =>
322- val localSig = keyManager.sign(claimAnchorTx.txInfo, keyManager.fundingPublicKey(cmd.commitment.localParams.fundingKeyPath, cmd.commitment.fundingTxIndex), TxOwner .Local , cmd.commitment.params.commitmentFormat)
323+ log.info(s " signing ${claimAnchorTx.txInfo.tx} with spentUtxos $spentUtxos" )
324+ val localSig = fundedTx.txInfo.input match {
325+ case _ : InputInfo .SegwitInput =>
326+ keyManager.sign(claimAnchorTx.txInfo, keyManager.fundingPublicKey(cmd.commitment.localParams.fundingKeyPath, cmd.commitment.fundingTxIndex), TxOwner .Local , cmd.commitment.params.commitmentFormat)
327+ case _ : InputInfo .TaprootInput =>
328+ if (claimAnchorTx.txInfo.fromRemoteCommitTx) {
329+ keyManager.sign(claimAnchorTx.txInfo, keyManager.paymentPoint(channelKeyPath), TxOwner .Local , cmd.commitment.params.commitmentFormat, spentUtxos)
330+ } else {
331+ val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, cmd.commitment.localCommit.index)
332+ keyManager.sign(claimAnchorTx.txInfo, keyManager.delayedPaymentPoint(channelKeyPath), localPerCommitmentPoint, TxOwner .Local , cmd.commitment.params.commitmentFormat, spentUtxos)
333+ }
334+ }
323335 val signedTx = claimAnchorTx.copy(txInfo = addSigs(claimAnchorTx.txInfo, localSig))
324336 signWalletInputs(signedTx, txFeerate, amountIn)
325337 case htlcTx : HtlcWithWitnessData =>
326338 val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, cmd.commitment.localCommit.index)
327339 val localHtlcBasepoint = keyManager.htlcPoint(channelKeyPath)
328- val localSig = keyManager.sign(htlcTx.txInfo, localHtlcBasepoint, localPerCommitmentPoint, TxOwner .Local , cmd.commitment.params.commitmentFormat)
340+ val localSig = htlcTx.txInfo.input match {
341+ case _ : InputInfo .SegwitInput =>
342+ keyManager.sign(htlcTx.txInfo, localHtlcBasepoint, localPerCommitmentPoint, TxOwner .Local , cmd.commitment.params.commitmentFormat)
343+ case _ : InputInfo .TaprootInput =>
344+ keyManager.sign(htlcTx.txInfo, localHtlcBasepoint, localPerCommitmentPoint, TxOwner .Local , cmd.commitment.params.commitmentFormat, spentUtxos)
345+ }
329346 val signedTx = htlcTx match {
330347 case htlcSuccess : HtlcSuccessWithWitnessData => htlcSuccess.copy(txInfo = addSigs(htlcSuccess.txInfo, localSig, htlcSuccess.remoteSig, htlcSuccess.preimage, cmd.commitment.params.commitmentFormat))
331348 case htlcTimeout : HtlcTimeoutWithWitnessData => htlcTimeout.copy(txInfo = addSigs(htlcTimeout.txInfo, localSig, htlcTimeout.remoteSig, cmd.commitment.params.commitmentFormat))
@@ -361,13 +378,17 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
361378 case InputInfo .SegwitInput (_, _, redeemScript) => fr.acinq.bitcoin.Script .parse(redeemScript)
362379 case _ : InputInfo .TaprootInput => null
363380 }
381+ val sigHash = locallySignedTx.txInfo.input match {
382+ case _ : InputInfo .TaprootInput => fr.acinq.bitcoin.SigHash .SIGHASH_DEFAULT
383+ case _ : InputInfo .SegwitInput => fr.acinq.bitcoin.SigHash .SIGHASH_ALL
384+ }
364385 val psbt = new Psbt (locallySignedTx.txInfo.tx)
365386 .updateWitnessInput(
366387 locallySignedTx.txInfo.input.outPoint,
367388 locallySignedTx.txInfo.input.txOut,
368389 null ,
369390 witnessScript,
370- fr.acinq.bitcoin. SigHash . SIGHASH_ALL ,
391+ sigHash ,
371392 java.util.Map .of(),
372393 null ,
373394 null ,
@@ -431,10 +452,26 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
431452 }
432453 }
433454
434- private def addInputs (tx : ReplaceableTxWithWalletInputs , targetFeerate : FeeratePerKw , commitment : FullCommitment ): Future [(ReplaceableTxWithWalletInputs , Satoshi )] = {
455+ private def addInputs (tx : ReplaceableTxWithWalletInputs , targetFeerate : FeeratePerKw , commitment : FullCommitment ): Future [(ReplaceableTxWithWalletInputs , Satoshi , Seq [TxOut ])] = {
456+ import fr .acinq .bitcoin .scalacompat .KotlinUtils ._
457+
458+ def getSpentUtxos (txInfo : TransactionWithInputInfo ): Future [Seq [TxOut ]] = {
459+ val psbt = new Psbt (txInfo.tx)
460+ bitcoinClient.utxoUpdatePsbt(psbt).map(updatedPsbt => {
461+ // we get the first spent input from txInfo.input, additional inputs are wallet inputs and have been updated by bitcoin core
462+ txInfo.input.txOut +: updatedPsbt.inputs.asScala.tail.map(_.getWitnessUtxo).map(kmp2scala).toSeq
463+ })
464+ }
465+
435466 tx match {
436- case anchorTx : ClaimLocalAnchorWithWitnessData => addInputs(anchorTx, targetFeerate, commitment)
437- case htlcTx : HtlcWithWitnessData => addInputs(htlcTx, targetFeerate, commitment)
467+ case anchorTx : ClaimLocalAnchorWithWitnessData => for {
468+ (fundedTx, amountIn) <- addInputs(anchorTx, targetFeerate, commitment)
469+ spentUtxos <- getSpentUtxos(fundedTx.txInfo)
470+ } yield (fundedTx, amountIn, spentUtxos)
471+ case htlcTx : HtlcWithWitnessData => for {
472+ (fundedTx, amountIn) <- addInputs(htlcTx, targetFeerate, commitment)
473+ spentUtxos <- getSpentUtxos(fundedTx.txInfo)
474+ } yield (fundedTx, amountIn, spentUtxos)
438475 }
439476 }
440477
0 commit comments