Skip to content
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: List[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
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ object InteractiveTxBuilder {
case class UnusableInput(outpoint: OutPoint)

/** Unsigned transaction created collaboratively. */
case class SharedTransaction(localInputs: Seq[TxAddInput], remoteInputs: Seq[RemoteTxAddInput], localOutputs: Seq[TxAddOutput], remoteOutputs: Seq[RemoteTxAddOutput], lockTime: Long) {
case class SharedTransaction(localInputs: List[TxAddInput], remoteInputs: List[RemoteTxAddInput], localOutputs: List[TxAddOutput], remoteOutputs: List[RemoteTxAddOutput], lockTime: Long) {
val localAmountIn: Satoshi = localInputs.map(i => i.previousTx.txOut(i.previousTxOutput.toInt).amount).sum
val remoteAmountIn: Satoshi = remoteInputs.map(_.txOut.amount).sum
val totalAmountIn: Satoshi = localAmountIn + remoteAmountIn
Expand Down Expand Up @@ -566,7 +566,7 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon
}

def validateTx(session: InteractiveTxSession): Either[ChannelException, (SharedTransaction, Int)] = {
val sharedTx = SharedTransaction(session.localInputs, session.remoteInputs.map(i => RemoteTxAddInput(i)), session.localOutputs, session.remoteOutputs.map(o => RemoteTxAddOutput(o)), fundingParams.lockTime)
val sharedTx = SharedTransaction(session.localInputs.toList, session.remoteInputs.map(i => RemoteTxAddInput(i)).toList, session.localOutputs.toList, session.remoteOutputs.map(o => RemoteTxAddOutput(o)).toList, fundingParams.lockTime)
val tx = sharedTx.buildUnsignedTx()

if (tx.txIn.length > 252 || tx.txOut.length > 252) {
Expand Down Expand Up @@ -689,6 +689,8 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon
Behaviors.stopped
}
case SignTransactionResult(signedTx, None) =>
// We return as soon as we sign the tx, because we need to be able to handle the case where remote publishes the
// tx right away without properly sending us their signature.
log.info("interactive-tx partially signed with {} local inputs, {} remote inputs, {} local outputs and {} remote outputs", signedTx.tx.localInputs.length, signedTx.tx.remoteInputs.length, signedTx.tx.localOutputs.length, signedTx.tx.remoteOutputs.length)
replyTo ! Succeeded(fundingParams, signedTx, commitments)
Behaviors.stopped
Expand Down
Loading