Skip to content

Commit

Permalink
Update to select funding inputs before sending open_channel2 and spli…
Browse files Browse the repository at this point in the history
…ce_init

 - If `addExcessToRecipientPosition_opt` is set, excess from funding (if any) will be added to the proposed `fundingAmount`/`fundingContribution` before sending `open_channel2`/`splice_init` respectively.
 - We assume our peer requires confirmed inputs. In the future we could add a heuristic for this, but it's safer to assume they want confirmed inputs.
  • Loading branch information
remyers committed Sep 10, 2024
1 parent 8cc2e4a commit dc102b5
Show file tree
Hide file tree
Showing 15 changed files with 357 additions and 104 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import fr.acinq.bitcoin.scalacompat.{ByteVector32, DeterministicWallet, OutPoint
import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerKw}
import fr.acinq.eclair.channel.LocalFundingStatus.DualFundedUnconfirmedFundingTx
import fr.acinq.eclair.channel.fund.InteractiveTxBuilder._
import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningSession}
import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxFunder, InteractiveTxSigningSession}
import fr.acinq.eclair.io.Peer
import fr.acinq.eclair.transactions.CommitmentSpec
import fr.acinq.eclair.transactions.Transactions._
Expand Down Expand Up @@ -62,6 +62,7 @@ case object WAIT_FOR_FUNDING_CONFIRMED extends ChannelState
case object WAIT_FOR_CHANNEL_READY extends ChannelState
// Dual-funded channel opening:
case object WAIT_FOR_INIT_DUAL_FUNDED_CHANNEL extends ChannelState
case object WAIT_FOR_DUAL_FUNDING_INTERNAL extends ChannelState
case object WAIT_FOR_OPEN_DUAL_FUNDED_CHANNEL extends ChannelState
case object WAIT_FOR_ACCEPT_DUAL_FUNDED_CHANNEL extends ChannelState
case object WAIT_FOR_DUAL_FUNDING_CREATED extends ChannelState
Expand Down Expand Up @@ -506,7 +507,7 @@ object SpliceStatus {
/** The channel is quiescent, we wait for our peer to send splice_init or tx_init_rbf. */
case object NonInitiatorQuiescent extends SpliceStatus
/** We told our peer we want to splice funds in the channel. */
case class SpliceRequested(cmd: CMD_SPLICE, init: SpliceInit) extends SpliceStatus
case class SpliceRequested(cmd: CMD_SPLICE, init: SpliceInit, fundingContributions_opt: Option[InteractiveTxFunder.FundingContributions]) extends SpliceStatus
/** We told our peer we want to RBF the latest splice transaction. */
case class RbfRequested(cmd: CMD_BUMP_FUNDING_FEE, rbf: TxInitRbf) extends SpliceStatus
/** We both agreed to splice/rbf and are building the corresponding transaction. */
Expand Down Expand Up @@ -583,10 +584,14 @@ final case class DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments: Commitments,
}
final case class DATA_WAIT_FOR_CHANNEL_READY(commitments: Commitments, shortIds: ShortIds) extends ChannelDataWithCommitments

final case class DATA_WAIT_FOR_DUAL_FUNDING_INTERNAL(input: INPUT_INIT_CHANNEL_INITIATOR) extends TransientChannelData {
val channelId: ByteVector32 = input.temporaryChannelId
}

final case class DATA_WAIT_FOR_OPEN_DUAL_FUNDED_CHANNEL(init: INPUT_INIT_CHANNEL_NON_INITIATOR) extends TransientChannelData {
val channelId: ByteVector32 = init.temporaryChannelId
}
final case class DATA_WAIT_FOR_ACCEPT_DUAL_FUNDED_CHANNEL(init: INPUT_INIT_CHANNEL_INITIATOR, lastSent: OpenDualFundedChannel) extends TransientChannelData {
final case class DATA_WAIT_FOR_ACCEPT_DUAL_FUNDED_CHANNEL(init: INPUT_INIT_CHANNEL_INITIATOR, lastSent: OpenDualFundedChannel, fundingContributions: InteractiveTxFunder.FundingContributions) extends TransientChannelData {
val channelId: ByteVector32 = lastSent.temporaryChannelId
}
final case class DATA_WAIT_FOR_DUAL_FUNDING_CREATED(channelId: ByteVector32,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import akka.actor.typed.scaladsl.adapter.{ClassicActorContextOps, actorRefAdapte
import akka.actor.{Actor, ActorContext, ActorRef, FSM, OneForOneStrategy, PossiblyHarmful, Props, SupervisorStrategy, typed}
import akka.event.Logging.MDC
import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.scalacompat.{ByteVector32, Satoshi, SatoshiLong, Transaction, TxId}
import fr.acinq.bitcoin.scalacompat.{ByteVector32, Satoshi, SatoshiLong, Script, Transaction, TxId}
import fr.acinq.eclair.Logs.LogCategory
import fr.acinq.eclair._
import fr.acinq.eclair.blockchain.OnChainWallet.MakeFundingTxResponse
Expand Down Expand Up @@ -944,7 +944,28 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
context.system.scheduler.scheduleOnce(2 second, peer, Peer.Disconnect(remoteNodeId))
stay() using d.copy(spliceStatus = SpliceStatus.NoSplice) sending Warning(d.channelId, f.getMessage)
case Right(spliceInit) =>
stay() using d.copy(spliceStatus = SpliceStatus.SpliceRequested(cmd, spliceInit)) sending spliceInit
// use our fundingPubKey as a placeholder for the remote funder's pubkey
val fundingPubKey = spliceInit.fundingPubKey
val parentCommitment = d.commitments.latest.commitment
// assume our peer requires confirmed inputs when we initiate a splice
val requireConfirmedInputs = RequireConfirmedInputs(forLocal = true, forRemote = nodeParams.channelConf.requireConfirmedInputsForDualFunding)
val fundingParams = InteractiveTxParams(
channelId = spliceInit.channelId,
isInitiator = true,
localContribution = spliceInit.fundingContribution,
remoteContribution = 0 sat,
sharedInput_opt = Some(Multisig2of2Input(parentCommitment)),
remoteFundingPubKey = fundingPubKey,
localOutputs = cmd.spliceOutputs,
lockTime = nodeParams.currentBlockHeight.toLong,
dustLimit = d.commitments.params.localParams.dustLimit,
targetFeerate = spliceInit.feerate,
requireConfirmedInputs = requireConfirmedInputs
)
val dummyFundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(fundingPubKey, fundingPubKey)))
val txFunder = context.spawnAnonymous(InteractiveTxFunder(remoteNodeId, fundingParams, dummyFundingPubkeyScript, purpose = InteractiveTxBuilder.SpliceTx(parentCommitment), wallet, nodeParams.channelConf.maxExcess_opt))
txFunder ! InteractiveTxFunder.FundTransaction(self)
stay() using d.copy(spliceStatus = SpliceStatus.SpliceRequested(cmd, spliceInit, None))
}
case cmd: CMD_BUMP_FUNDING_FEE => initiateSpliceRbf(cmd, d) match {
case Left(f) =>
Expand Down Expand Up @@ -1018,6 +1039,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
channelParams = d.commitments.params,
purpose = InteractiveTxBuilder.SpliceTx(parentCommitment),
localPushAmount = spliceAck.pushAmount, remotePushAmount = msg.pushAmount,
None,
wallet
))
txBuilder ! InteractiveTxBuilder.Start(self)
Expand All @@ -1036,7 +1058,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with

case Event(msg: SpliceAck, d: DATA_NORMAL) =>
d.spliceStatus match {
case SpliceStatus.SpliceRequested(cmd, spliceInit) =>
case SpliceStatus.SpliceRequested(cmd, spliceInit, fundingContributions_opt) =>
log.info("our peer accepted our splice request and will contribute {} to the funding transaction", msg.fundingContribution)
val parentCommitment = d.commitments.latest.commitment
val fundingParams = InteractiveTxParams(
Expand All @@ -1059,6 +1081,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
channelParams = d.commitments.params,
purpose = InteractiveTxBuilder.SpliceTx(parentCommitment),
localPushAmount = cmd.pushAmount, remotePushAmount = msg.pushAmount,
fundingContributions_opt = fundingContributions_opt,
wallet
))
txBuilder ! InteractiveTxBuilder.Start(self)
Expand All @@ -1068,6 +1091,21 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
stay()
}

case Event(msg: InteractiveTxFunder.Response, d: DATA_NORMAL) =>
d.spliceStatus match {
case SpliceStatus.SpliceRequested(cmd, spliceInit, _) =>
msg match {
case InteractiveTxFunder.FundingFailed =>
cmd.replyTo ! RES_FAILURE(cmd, ChannelFundingError(d.channelId))
stay() using d.copy(spliceStatus = SpliceStatus.NoSplice) calling endQuiescence(d)
case fundingContributions: InteractiveTxFunder.FundingContributions =>
stay() using d.copy(spliceStatus = SpliceStatus.SpliceRequested(cmd, spliceInit, Some(fundingContributions))) sending spliceInit
}
case _ =>
log.warning("received unexpected response from txFunder: {}, current splice status is {}", msg, d.spliceStatus)
stay() using d.copy(spliceStatus = SpliceStatus.NoSplice) calling endQuiescence(d)
}

case Event(msg: InteractiveTxConstructionMessage, d: DATA_NORMAL) =>
d.spliceStatus match {
case SpliceStatus.SpliceInProgress(_, _, txBuilder, _) =>
Expand Down Expand Up @@ -1116,6 +1154,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
channelParams = d.commitments.params,
purpose = rbf,
localPushAmount = 0 msat, remotePushAmount = 0 msat,
None,
wallet
))
txBuilder ! InteractiveTxBuilder.Start(self)
Expand Down Expand Up @@ -1160,6 +1199,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
channelParams = d.commitments.params,
purpose = rbf,
localPushAmount = 0 msat, remotePushAmount = 0 msat,
None,
wallet
))
txBuilder ! InteractiveTxBuilder.Start(self)
Expand All @@ -1183,7 +1223,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
log.info("our peer aborted the splice attempt: ascii='{}' bin={}", msg.toAscii, msg.data)
rollbackFundingAttempt(signingSession.fundingTx.tx, previousTxs = Seq.empty) // no splice rbf yet
stay() using d.copy(spliceStatus = SpliceStatus.NoSplice) sending TxAbort(d.channelId, SpliceAttemptAborted(d.channelId).getMessage) calling endQuiescence(d)
case SpliceStatus.SpliceRequested(cmd, _) =>
case SpliceStatus.SpliceRequested(cmd, _, _) =>
log.info("our peer rejected our splice attempt: ascii='{}' bin={}", msg.toAscii, msg.data)
cmd.replyTo ! RES_FAILURE(cmd, new RuntimeException(s"splice attempt rejected by our peer: ${msg.toAscii}"))
stay() using d.copy(spliceStatus = SpliceStatus.NoSplice) sending TxAbort(d.channelId, SpliceAttemptAborted(d.channelId).getMessage) calling endQuiescence(d)
Expand Down Expand Up @@ -2996,7 +3036,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
private def reportSpliceFailure(spliceStatus: SpliceStatus, f: Throwable): Unit = {
val cmd_opt = spliceStatus match {
case SpliceStatus.NegotiatingQuiescence(cmd_opt, _) => cmd_opt
case SpliceStatus.SpliceRequested(cmd, _) => Some(cmd)
case SpliceStatus.SpliceRequested(cmd, _, _) => Some(cmd)
case SpliceStatus.RbfRequested(cmd, _) => Some(cmd)
case SpliceStatus.SpliceInProgress(cmd_opt, _, txBuilder, _) =>
txBuilder ! InteractiveTxBuilder.Abort
Expand Down
Loading

0 comments on commit dc102b5

Please sign in to comment.