Skip to content

Commit 81ab7d2

Browse files
committed
Update to select funding inputs before sending open_channel2 and splice_init
- 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.
1 parent 8cc2e4a commit 81ab7d2

14 files changed

+250
-104
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import fr.acinq.bitcoin.scalacompat.{ByteVector32, DeterministicWallet, OutPoint
2222
import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerKw}
2323
import fr.acinq.eclair.channel.LocalFundingStatus.DualFundedUnconfirmedFundingTx
2424
import fr.acinq.eclair.channel.fund.InteractiveTxBuilder._
25-
import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningSession}
25+
import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxFunder, InteractiveTxSigningSession}
2626
import fr.acinq.eclair.io.Peer
2727
import fr.acinq.eclair.transactions.CommitmentSpec
2828
import fr.acinq.eclair.transactions.Transactions._
@@ -62,6 +62,7 @@ case object WAIT_FOR_FUNDING_CONFIRMED extends ChannelState
6262
case object WAIT_FOR_CHANNEL_READY extends ChannelState
6363
// Dual-funded channel opening:
6464
case object WAIT_FOR_INIT_DUAL_FUNDED_CHANNEL extends ChannelState
65+
case object WAIT_FOR_DUAL_FUNDING_INTERNAL extends ChannelState
6566
case object WAIT_FOR_OPEN_DUAL_FUNDED_CHANNEL extends ChannelState
6667
case object WAIT_FOR_ACCEPT_DUAL_FUNDED_CHANNEL extends ChannelState
6768
case object WAIT_FOR_DUAL_FUNDING_CREATED extends ChannelState
@@ -506,7 +507,7 @@ object SpliceStatus {
506507
/** The channel is quiescent, we wait for our peer to send splice_init or tx_init_rbf. */
507508
case object NonInitiatorQuiescent extends SpliceStatus
508509
/** We told our peer we want to splice funds in the channel. */
509-
case class SpliceRequested(cmd: CMD_SPLICE, init: SpliceInit) extends SpliceStatus
510+
case class SpliceRequested(cmd: CMD_SPLICE, init: SpliceInit, fundingContributions_opt: Option[InteractiveTxFunder.FundingContributions]) extends SpliceStatus
510511
/** We told our peer we want to RBF the latest splice transaction. */
511512
case class RbfRequested(cmd: CMD_BUMP_FUNDING_FEE, rbf: TxInitRbf) extends SpliceStatus
512513
/** We both agreed to splice/rbf and are building the corresponding transaction. */
@@ -583,10 +584,14 @@ final case class DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments: Commitments,
583584
}
584585
final case class DATA_WAIT_FOR_CHANNEL_READY(commitments: Commitments, shortIds: ShortIds) extends ChannelDataWithCommitments
585586

587+
final case class DATA_WAIT_FOR_DUAL_FUNDING_INTERNAL(input: INPUT_INIT_CHANNEL_INITIATOR) extends TransientChannelData {
588+
val channelId: ByteVector32 = input.temporaryChannelId
589+
}
590+
586591
final case class DATA_WAIT_FOR_OPEN_DUAL_FUNDED_CHANNEL(init: INPUT_INIT_CHANNEL_NON_INITIATOR) extends TransientChannelData {
587592
val channelId: ByteVector32 = init.temporaryChannelId
588593
}
589-
final case class DATA_WAIT_FOR_ACCEPT_DUAL_FUNDED_CHANNEL(init: INPUT_INIT_CHANNEL_INITIATOR, lastSent: OpenDualFundedChannel) extends TransientChannelData {
594+
final case class DATA_WAIT_FOR_ACCEPT_DUAL_FUNDED_CHANNEL(init: INPUT_INIT_CHANNEL_INITIATOR, lastSent: OpenDualFundedChannel, fundingContributions: InteractiveTxFunder.FundingContributions) extends TransientChannelData {
590595
val channelId: ByteVector32 = lastSent.temporaryChannelId
591596
}
592597
final case class DATA_WAIT_FOR_DUAL_FUNDING_CREATED(channelId: ByteVector32,

eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import akka.actor.typed.scaladsl.adapter.{ClassicActorContextOps, actorRefAdapte
2121
import akka.actor.{Actor, ActorContext, ActorRef, FSM, OneForOneStrategy, PossiblyHarmful, Props, SupervisorStrategy, typed}
2222
import akka.event.Logging.MDC
2323
import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey}
24-
import fr.acinq.bitcoin.scalacompat.{ByteVector32, Satoshi, SatoshiLong, Transaction, TxId}
24+
import fr.acinq.bitcoin.scalacompat.{ByteVector32, Satoshi, SatoshiLong, Script, Transaction, TxId}
2525
import fr.acinq.eclair.Logs.LogCategory
2626
import fr.acinq.eclair._
2727
import fr.acinq.eclair.blockchain.OnChainWallet.MakeFundingTxResponse
@@ -944,7 +944,28 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
944944
context.system.scheduler.scheduleOnce(2 second, peer, Peer.Disconnect(remoteNodeId))
945945
stay() using d.copy(spliceStatus = SpliceStatus.NoSplice) sending Warning(d.channelId, f.getMessage)
946946
case Right(spliceInit) =>
947-
stay() using d.copy(spliceStatus = SpliceStatus.SpliceRequested(cmd, spliceInit)) sending spliceInit
947+
// use our fundingPubKey as a placeholder for the remote funder's pubkey
948+
val fundingPubKey = spliceInit.fundingPubKey
949+
val parentCommitment = d.commitments.latest.commitment
950+
// assume our peer requires confirmed inputs when we initiate a splice
951+
val requireConfirmedInputs = RequireConfirmedInputs(forLocal = true, forRemote = nodeParams.channelConf.requireConfirmedInputsForDualFunding)
952+
val fundingParams = InteractiveTxParams(
953+
channelId = spliceInit.channelId,
954+
isInitiator = true,
955+
localContribution = spliceInit.fundingContribution,
956+
remoteContribution = 0 sat,
957+
sharedInput_opt = Some(Multisig2of2Input(parentCommitment)),
958+
remoteFundingPubKey = fundingPubKey,
959+
localOutputs = cmd.spliceOutputs,
960+
lockTime = nodeParams.currentBlockHeight.toLong,
961+
dustLimit = d.commitments.params.localParams.dustLimit,
962+
targetFeerate = spliceInit.feerate,
963+
requireConfirmedInputs = requireConfirmedInputs
964+
)
965+
val dummyFundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(fundingPubKey, fundingPubKey)))
966+
val txFunder = context.spawnAnonymous(InteractiveTxFunder(remoteNodeId, fundingParams, dummyFundingPubkeyScript, purpose = InteractiveTxBuilder.SpliceTx(parentCommitment), wallet, nodeParams.channelConf.maxExcess_opt))
967+
txFunder ! InteractiveTxFunder.FundTransaction(self)
968+
stay() using d.copy(spliceStatus = SpliceStatus.SpliceRequested(cmd, spliceInit, None))
948969
}
949970
case cmd: CMD_BUMP_FUNDING_FEE => initiateSpliceRbf(cmd, d) match {
950971
case Left(f) =>
@@ -1018,6 +1039,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
10181039
channelParams = d.commitments.params,
10191040
purpose = InteractiveTxBuilder.SpliceTx(parentCommitment),
10201041
localPushAmount = spliceAck.pushAmount, remotePushAmount = msg.pushAmount,
1042+
None,
10211043
wallet
10221044
))
10231045
txBuilder ! InteractiveTxBuilder.Start(self)
@@ -1036,7 +1058,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
10361058

10371059
case Event(msg: SpliceAck, d: DATA_NORMAL) =>
10381060
d.spliceStatus match {
1039-
case SpliceStatus.SpliceRequested(cmd, spliceInit) =>
1061+
case SpliceStatus.SpliceRequested(cmd, spliceInit, fundingContributions_opt) =>
10401062
log.info("our peer accepted our splice request and will contribute {} to the funding transaction", msg.fundingContribution)
10411063
val parentCommitment = d.commitments.latest.commitment
10421064
val fundingParams = InteractiveTxParams(
@@ -1059,6 +1081,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
10591081
channelParams = d.commitments.params,
10601082
purpose = InteractiveTxBuilder.SpliceTx(parentCommitment),
10611083
localPushAmount = cmd.pushAmount, remotePushAmount = msg.pushAmount,
1084+
fundingContributions_opt = fundingContributions_opt,
10621085
wallet
10631086
))
10641087
txBuilder ! InteractiveTxBuilder.Start(self)
@@ -1068,6 +1091,21 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
10681091
stay()
10691092
}
10701093

1094+
case Event(msg: InteractiveTxFunder.Response, d: DATA_NORMAL) =>
1095+
d.spliceStatus match {
1096+
case SpliceStatus.SpliceRequested(cmd, spliceInit, _) =>
1097+
msg match {
1098+
case InteractiveTxFunder.FundingFailed =>
1099+
cmd.replyTo ! RES_FAILURE(cmd, ChannelFundingError(d.channelId))
1100+
stay() using d.copy(spliceStatus = SpliceStatus.NoSplice) calling endQuiescence(d)
1101+
case fundingContributions: InteractiveTxFunder.FundingContributions =>
1102+
stay() using d.copy(spliceStatus = SpliceStatus.SpliceRequested(cmd, spliceInit, Some(fundingContributions))) sending spliceInit
1103+
}
1104+
case _ =>
1105+
log.warning("received unexpected response from txFunder: {}, current splice status is {}", msg, d.spliceStatus)
1106+
stay() using d.copy(spliceStatus = SpliceStatus.NoSplice) calling endQuiescence(d)
1107+
}
1108+
10711109
case Event(msg: InteractiveTxConstructionMessage, d: DATA_NORMAL) =>
10721110
d.spliceStatus match {
10731111
case SpliceStatus.SpliceInProgress(_, _, txBuilder, _) =>
@@ -1116,6 +1154,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
11161154
channelParams = d.commitments.params,
11171155
purpose = rbf,
11181156
localPushAmount = 0 msat, remotePushAmount = 0 msat,
1157+
None,
11191158
wallet
11201159
))
11211160
txBuilder ! InteractiveTxBuilder.Start(self)
@@ -1160,6 +1199,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
11601199
channelParams = d.commitments.params,
11611200
purpose = rbf,
11621201
localPushAmount = 0 msat, remotePushAmount = 0 msat,
1202+
None,
11631203
wallet
11641204
))
11651205
txBuilder ! InteractiveTxBuilder.Start(self)
@@ -1183,7 +1223,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
11831223
log.info("our peer aborted the splice attempt: ascii='{}' bin={}", msg.toAscii, msg.data)
11841224
rollbackFundingAttempt(signingSession.fundingTx.tx, previousTxs = Seq.empty) // no splice rbf yet
11851225
stay() using d.copy(spliceStatus = SpliceStatus.NoSplice) sending TxAbort(d.channelId, SpliceAttemptAborted(d.channelId).getMessage) calling endQuiescence(d)
1186-
case SpliceStatus.SpliceRequested(cmd, _) =>
1226+
case SpliceStatus.SpliceRequested(cmd, _, _) =>
11871227
log.info("our peer rejected our splice attempt: ascii='{}' bin={}", msg.toAscii, msg.data)
11881228
cmd.replyTo ! RES_FAILURE(cmd, new RuntimeException(s"splice attempt rejected by our peer: ${msg.toAscii}"))
11891229
stay() using d.copy(spliceStatus = SpliceStatus.NoSplice) sending TxAbort(d.channelId, SpliceAttemptAborted(d.channelId).getMessage) calling endQuiescence(d)
@@ -2996,7 +3036,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
29963036
private def reportSpliceFailure(spliceStatus: SpliceStatus, f: Throwable): Unit = {
29973037
val cmd_opt = spliceStatus match {
29983038
case SpliceStatus.NegotiatingQuiescence(cmd_opt, _) => cmd_opt
2999-
case SpliceStatus.SpliceRequested(cmd, _) => Some(cmd)
3039+
case SpliceStatus.SpliceRequested(cmd, _, _) => Some(cmd)
30003040
case SpliceStatus.RbfRequested(cmd, _) => Some(cmd)
30013041
case SpliceStatus.SpliceInProgress(cmd_opt, _, txBuilder, _) =>
30023042
txBuilder ! InteractiveTxBuilder.Abort

0 commit comments

Comments
 (0)