Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dual funding channel confirmation #2274

Merged
merged 11 commits into from
Aug 19, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ object CheckBalance {
.foldLeft(OffChainBalance()) {
case (r, d: DATA_WAIT_FOR_FUNDING_CONFIRMED) => r.modify(_.waitForFundingConfirmed).using(updateMainBalance(d.commitments.localCommit))
case (r, d: DATA_WAIT_FOR_CHANNEL_READY) => r.modify(_.waitForChannelReady).using(updateMainBalance(d.commitments.localCommit))
case (r, d: DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED) => r.modify(_.waitForFundingConfirmed).using(updateMainBalance(d.commitments.localCommit))
case (r, d: DATA_WAIT_FOR_DUAL_FUNDING_READY) => r.modify(_.waitForChannelReady).using(updateMainBalance(d.commitments.localCommit))
case (r, d: DATA_NORMAL) => r.modify(_.normal).using(updateMainAndHtlcBalance(d.commitments, knownPreimages))
case (r, d: DATA_SHUTDOWN) => r.modify(_.shutdown).using(updateMainAndHtlcBalance(d.commitments, knownPreimages))
case (r, d: DATA_NEGOTIATING) => r.modify(_.negotiating).using(updateMainBalance(d.commitments.localCommit))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@

package fr.acinq.eclair.channel

import akka.actor.typed
import akka.actor.{ActorRef, PossiblyHarmful}
import akka.actor.{ActorRef, PossiblyHarmful, typed}
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
import fr.acinq.bitcoin.scalacompat.{ByteVector32, DeterministicWallet, OutPoint, Satoshi, Transaction}
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import fr.acinq.eclair.channel.InteractiveTxBuilder.{InteractiveTxParams, SignedSharedTransaction}
import fr.acinq.eclair.channel.InteractiveTxBuilder._
import fr.acinq.eclair.payment.OutgoingPaymentPacket.Upstream
import fr.acinq.eclair.transactions.CommitmentSpec
import fr.acinq.eclair.transactions.Transactions._
Expand Down Expand Up @@ -63,7 +62,8 @@ case object WAIT_FOR_INIT_DUAL_FUNDED_CHANNEL 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
case object WAIT_FOR_DUAL_FUNDING_PLACEHOLDER extends ChannelState
case object WAIT_FOR_DUAL_FUNDING_CONFIRMED extends ChannelState
case object WAIT_FOR_DUAL_FUNDING_READY extends ChannelState
// Channel opened:
case object NORMAL extends ChannelState
case object SHUTDOWN extends ChannelState
Expand Down Expand Up @@ -479,16 +479,22 @@ final case class DATA_WAIT_FOR_ACCEPT_DUAL_FUNDED_CHANNEL(init: INPUT_INIT_CHANN
final case class DATA_WAIT_FOR_DUAL_FUNDING_CREATED(channelId: ByteVector32,
txBuilder: typed.ActorRef[InteractiveTxBuilder.Command],
deferred: Option[ChannelReady]) extends TransientChannelData
final case class DATA_WAIT_FOR_DUAL_FUNDING_PLACEHOLDER(commitments: Commitments,
fundingTx: SignedSharedTransaction,
fundingParams: InteractiveTxParams,
previousFundingTxs: Seq[DualFundingTx],
waitingSince: BlockHeight, // how long have we been waiting for a funding tx to confirm
lastChecked: BlockHeight, // last time we checked if the channel was double-spent
rbfAttempt: Option[typed.ActorRef[InteractiveTxBuilder.Command]],
deferred: Option[ChannelReady]) extends TransientChannelData {
val channelId: ByteVector32 = commitments.channelId
final case class DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED(commitments: Commitments,
fundingTx: SignedSharedTransaction,
fundingParams: InteractiveTxParams,
previousFundingTxs: Seq[DualFundingTx],
waitingSince: BlockHeight, // how long have we been waiting for a funding tx to confirm
lastChecked: BlockHeight, // last time we checked if the channel was double-spent
rbfAttempt: Option[typed.ActorRef[InteractiveTxBuilder.Command]],
deferred: Option[ChannelReady]) extends PersistentChannelData {
val signedFundingTx_opt: Option[Transaction] = fundingTx match {
case _: PartiallySignedSharedTransaction => None
case tx: FullySignedSharedTransaction => Some(tx.signedTx)
}
}
final case class DATA_WAIT_FOR_DUAL_FUNDING_READY(commitments: Commitments,
shortIds: ShortIds,
lastSent: ChannelReady) extends PersistentChannelData

final case class DATA_NORMAL(commitments: Commitments,
shortIds: ShortIds,
Expand All @@ -508,6 +514,7 @@ final case class DATA_NEGOTIATING(commitments: Commitments,
final case class DATA_CLOSING(commitments: Commitments,
fundingTx: Option[Transaction], // this will be non-empty if we are the initiator and we got in closing while waiting for our own tx to be published
waitingSince: BlockHeight, // how long since we initiated the closing
alternativeCommitments: List[Commitments], // commitments we signed that spend a different funding output
mutualCloseProposed: List[ClosingTx], // all exchanged closing sigs are flattened, we use this only to keep track of what publishable tx they have
mutualClosePublished: List[ClosingTx] = Nil,
localCommitPublished: Option[LocalCommitPublished] = None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,10 +212,18 @@ case class Commitments(channelId: ByteVector32,
val capacity: Satoshi = commitInput.txOut.amount

/** Channel reserve that applies to our funds. */
val localChannelReserve: Satoshi = remoteParams.requestedChannelReserve_opt.getOrElse(0 sat)
val localChannelReserve: Satoshi = if (channelFeatures.hasFeature(Features.DualFunding)) {
(capacity / 100).max(remoteParams.dustLimit)
} else {
remoteParams.requestedChannelReserve_opt.getOrElse(0 sat)
}

/** Channel reserve that applies to our peer's funds. */
val remoteChannelReserve: Satoshi = localParams.requestedChannelReserve_opt.getOrElse(0 sat)
val remoteChannelReserve: Satoshi = if (channelFeatures.hasFeature(Features.DualFunding)) {
(capacity / 100).max(localParams.dustLimit)
} else {
localParams.requestedChannelReserve_opt.getOrElse(0 sat)
}

// NB: when computing availableBalanceForSend and availableBalanceForReceive, the initiator keeps an extra buffer on
// top of its usual channel reserve to avoid getting channels stuck in case the on-chain feerate increases (see
Expand Down
19 changes: 13 additions & 6 deletions eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ object Helpers {
remoteParams = data.commitments.remoteParams.copy(initFeatures = remoteInit.features))
data match {
case d: DATA_WAIT_FOR_FUNDING_CONFIRMED => d.copy(commitments = commitments1)
case d: DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED => d.copy(commitments = commitments1)
case d: DATA_WAIT_FOR_CHANNEL_READY => d.copy(commitments = commitments1)
case d: DATA_WAIT_FOR_DUAL_FUNDING_READY => d.copy(commitments = commitments1)
case d: DATA_NORMAL => d.copy(commitments = commitments1)
case d: DATA_SHUTDOWN => d.copy(commitments = commitments1)
case d: DATA_NEGOTIATING => d.copy(commitments = commitments1)
Expand Down Expand Up @@ -560,12 +562,17 @@ object Helpers {
*
* @return true if channel was never open, or got closed immediately, had never any htlcs and local never had a positive balance
*/
def nothingAtStake(data: PersistentChannelData): Boolean =
data.commitments.localCommit.index == 0 &&
data.commitments.localCommit.spec.toLocal == 0.msat &&
data.commitments.remoteCommit.index == 0 &&
data.commitments.remoteCommit.spec.toRemote == 0.msat &&
data.commitments.remoteNextCommitInfo.isRight
def nothingAtStake(data: PersistentChannelData): Boolean = data match {
case d: DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED => (d.commitments +: d.previousFundingTxs.map(_.commitments)).forall(commitments => nothingAtStake(commitments))
case _ => nothingAtStake(data.commitments)
}

def nothingAtStake(commitments: Commitments): Boolean =
commitments.localCommit.index == 0 &&
commitments.localCommit.spec.toLocal == 0.msat &&
commitments.remoteCommit.index == 0 &&
commitments.remoteCommit.spec.toRemote == 0.msat &&
commitments.remoteNextCommitInfo.isRight

/**
* As soon as a tx spending the funding tx has reached min_depth, we know what the closing type will be, before
Expand Down
Loading