Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion eclair-core/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,12 @@ eclair {
dust-limit-satoshis = 546
max-remote-dust-limit-satoshis = 600
htlc-minimum-msat = 1
// The following parameters apply to each HTLC direction (incoming or outgoing), which means that the total HTLC limits will be twice what is set here
// The following parameters apply to each HTLC direction (incoming or outgoing), which means that the maximum amount in flight will be at most twice what is set here.
// Note that our peer may use a lower value than ours, which would reduce the maximum amount in flight.
// The smallest value of max-htlc-value-in-flight-msat and max-htlc-value-in-flight-percent will be applied when opening channels.
// If for example you open a 60 mBTC channel, eclair will set max-htlc-value-in-flight to 27 mBTC.
max-htlc-value-in-flight-msat = 5000000000 // 50 mBTC
max-htlc-value-in-flight-percent = 45 // 45% of the channel capacity
max-accepted-htlcs = 30

reserve-to-funding-ratio = 0.01 // recommended by BOLT #2
Expand Down
3 changes: 2 additions & 1 deletion eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,8 @@ object NodeParams extends Logging {
dustLimit = dustLimitSatoshis,
maxRemoteDustLimit = Satoshi(config.getLong("channel.max-remote-dust-limit-satoshis")),
htlcMinimum = htlcMinimum,
maxHtlcValueInFlightMsat = UInt64(config.getLong("channel.max-htlc-value-in-flight-msat")),
maxHtlcValueInFlightMsat = MilliSatoshi(config.getLong("channel.max-htlc-value-in-flight-msat")),
maxHtlcValueInFlightPercent = config.getInt("channel.max-htlc-value-in-flight-percent"),
maxAcceptedHtlcs = maxAcceptedHtlcs,
reserveToFundingRatio = config.getDouble("channel.reserve-to-funding-ratio"),
maxReserveToFundingRatio = config.getDouble("channel.max-reserve-to-funding-ratio"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ final case class DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT(commitments: Com
case class LocalParams(nodeId: PublicKey,
fundingKeyPath: DeterministicWallet.KeyPath,
dustLimit: Satoshi,
maxHtlcValueInFlightMsat: UInt64, // this is not MilliSatoshi because it can exceed the total amount of MilliSatoshi
maxHtlcValueInFlightMsat: MilliSatoshi,
Comment thread
t-bast marked this conversation as resolved.
requestedChannelReserve_opt: Option[Satoshi],
htlcMinimum: MilliSatoshi,
toSelfDelay: CltvExpiryDelta,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ case class UnexpectedHtlcId (override val channelId: Byte
case class ExpiryTooSmall (override val channelId: ByteVector32, minimum: CltvExpiry, actual: CltvExpiry, blockHeight: BlockHeight) extends ChannelException(channelId, s"expiry too small: minimum=$minimum actual=$actual blockHeight=$blockHeight")
case class ExpiryTooBig (override val channelId: ByteVector32, maximum: CltvExpiry, actual: CltvExpiry, blockHeight: BlockHeight) extends ChannelException(channelId, s"expiry too big: maximum=$maximum actual=$actual blockHeight=$blockHeight")
case class HtlcValueTooSmall (override val channelId: ByteVector32, minimum: MilliSatoshi, actual: MilliSatoshi) extends ChannelException(channelId, s"htlc value too small: minimum=$minimum actual=$actual")
case class HtlcValueTooHighInFlight (override val channelId: ByteVector32, maximum: UInt64, actual: MilliSatoshi) extends ChannelException(channelId, s"in-flight htlcs hold too much value: maximum=$maximum actual=$actual")
case class HtlcValueTooHighInFlight (override val channelId: ByteVector32, maximum: MilliSatoshi, actual: MilliSatoshi) extends ChannelException(channelId, s"in-flight htlcs hold too much value: maximum=$maximum actual=$actual")
case class TooManyAcceptedHtlcs (override val channelId: ByteVector32, maximum: Long) extends ChannelException(channelId, s"too many accepted htlcs: maximum=$maximum")
case class LocalDustHtlcExposureTooHigh (override val channelId: ByteVector32, maximum: Satoshi, actual: MilliSatoshi) extends ChannelException(channelId, s"dust htlcs hold too much value: maximum=$maximum actual=$actual")
case class RemoteDustHtlcExposureTooHigh (override val channelId: ByteVector32, maximum: Satoshi, actual: MilliSatoshi) extends ChannelException(channelId, s"dust htlcs hold too much value: maximum=$maximum actual=$actual")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,9 @@ case class Commitments(channelId: ByteVector32,

val capacity: Satoshi = commitInput.txOut.amount

// We can safely cast to millisatoshis since we verify that it's less than a valid millisatoshi amount.
val maxHtlcAmount: MilliSatoshi = remoteParams.maxHtlcValueInFlightMsat.toBigInt.min(localParams.maxHtlcValueInFlightMsat.toLong).toLong.msat

/** Channel reserve that applies to our funds. */
val localChannelReserve: Satoshi = if (channelFeatures.hasFeature(Features.DualFunding)) {
(capacity / 100).max(remoteParams.dustLimit)
Expand Down Expand Up @@ -406,9 +409,9 @@ object Commitments {
// We apply local *and* remote restrictions, to ensure both peers are happy with the resulting number of HTLCs.
// NB: we need the `toSeq` because otherwise duplicate amountMsat would be removed (since outgoingHtlcs is a Set).
val htlcValueInFlight = outgoingHtlcs.toSeq.map(_.amountMsat).sum
if (Seq(commitments1.localParams.maxHtlcValueInFlightMsat, commitments1.remoteParams.maxHtlcValueInFlightMsat).min < htlcValueInFlight) {
// TODO: this should be a specific UPDATE error (but it would require a spec change)
return Left(HtlcValueTooHighInFlight(commitments.channelId, maximum = Seq(commitments1.localParams.maxHtlcValueInFlightMsat, commitments1.remoteParams.maxHtlcValueInFlightMsat).min, actual = htlcValueInFlight))
val allowedHtlcValueInFlight = commitments1.maxHtlcAmount
if (allowedHtlcValueInFlight < htlcValueInFlight) {
return Left(HtlcValueTooHighInFlight(commitments.channelId, maximum = allowedHtlcValueInFlight, actual = htlcValueInFlight))
}
if (Seq(commitments1.localParams.maxAcceptedHtlcs, commitments1.remoteParams.maxAcceptedHtlcs).min < outgoingHtlcs.size) {
return Left(TooManyAcceptedHtlcs(commitments.channelId, maximum = Seq(commitments1.localParams.maxAcceptedHtlcs, commitments1.remoteParams.maxAcceptedHtlcs).min))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ object Channel {
dustLimit: Satoshi,
maxRemoteDustLimit: Satoshi,
htlcMinimum: MilliSatoshi,
maxHtlcValueInFlightMsat: UInt64,
maxHtlcValueInFlightMsat: MilliSatoshi,
maxHtlcValueInFlightPercent: Int,
maxAcceptedHtlcs: Int,
reserveToFundingRatio: Double,
maxReserveToFundingRatio: Double,
Expand All @@ -81,6 +82,8 @@ object Channel {
unhandledExceptionStrategy: UnhandledExceptionStrategy,
revocationTimeout: FiniteDuration,
requireConfirmedInputsForDualFunding: Boolean) {
require(0 <= maxHtlcValueInFlightPercent && maxHtlcValueInFlightPercent <= 100, "max-htlc-value-in-flight-percent must be between 0 and 100")

def minFundingSatoshis(announceChannel: Boolean): Satoshi = if (announceChannel) minFundingPublicSatoshis else minFundingPrivateSatoshis
}

Expand Down Expand Up @@ -635,7 +638,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val
val channelUpdate1 = if (d.channelUpdate.shortChannelId != scidForChannelUpdate) {
log.info(s"using new scid in channel_update: old=${d.channelUpdate.shortChannelId} new=$scidForChannelUpdate")
// we re-announce the channelUpdate for the same reason
Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate, d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.capacity.toMilliSatoshi, isPrivate = !d.commitments.announceChannel, enable = Helpers.aboveReserve(d.commitments))
Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate, d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.maxHtlcAmount, isPrivate = !d.commitments.announceChannel, enable = Helpers.aboveReserve(d.commitments))
Comment thread
t-bast marked this conversation as resolved.
} else {
d.channelUpdate
}
Expand Down Expand Up @@ -668,7 +671,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val
} else {
// we generate a new channel_update because the scid used may change if we were previously using an alias
val scidForChannelUpdate = Helpers.scidForChannelUpdate(Some(channelAnn), d.shortIds.localAlias)
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate, d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.capacity.toMilliSatoshi, isPrivate = false, enable = Helpers.aboveReserve(d.commitments))
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate, d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.maxHtlcAmount, isPrivate = false, enable = Helpers.aboveReserve(d.commitments))
// we use goto() instead of stay() because we want to fire transitions
goto(NORMAL) using d.copy(channelAnnouncement = Some(channelAnn), channelUpdate = channelUpdate) storing()
}
Expand All @@ -690,7 +693,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val
}

case Event(c: CMD_UPDATE_RELAY_FEE, d: DATA_NORMAL) =>
val channelUpdate1 = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate(d), c.cltvExpiryDelta_opt.getOrElse(d.channelUpdate.cltvExpiryDelta), d.channelUpdate.htlcMinimumMsat, c.feeBase, c.feeProportionalMillionths, d.commitments.capacity.toMilliSatoshi, isPrivate = !d.commitments.announceChannel, enable = Helpers.aboveReserve(d.commitments))
val channelUpdate1 = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate(d), c.cltvExpiryDelta_opt.getOrElse(d.channelUpdate.cltvExpiryDelta), d.channelUpdate.htlcMinimumMsat, c.feeBase, c.feeProportionalMillionths, d.commitments.maxHtlcAmount, isPrivate = !d.commitments.announceChannel, enable = Helpers.aboveReserve(d.commitments))
log.info(s"updating relay fees: prev={} next={}", d.channelUpdate.toStringShort, channelUpdate1.toStringShort)
val replyTo = if (c.replyTo == ActorRef.noSender) sender() else c.replyTo
replyTo ! RES_SUCCESS(c, d.channelId)
Expand All @@ -699,7 +702,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val

case Event(BroadcastChannelUpdate(reason), d: DATA_NORMAL) =>
val age = TimestampSecond.now() - d.channelUpdate.timestamp
val channelUpdate1 = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate(d), d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.capacity.toMilliSatoshi, isPrivate = !d.commitments.announceChannel, enable = Helpers.aboveReserve(d.commitments))
val channelUpdate1 = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate(d), d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.maxHtlcAmount, isPrivate = !d.commitments.announceChannel, enable = Helpers.aboveReserve(d.commitments))
reason match {
case Reconnected if d.commitments.announceChannel && Announcements.areSame(channelUpdate1, d.channelUpdate) && age < REFRESH_CHANNEL_UPDATE_INTERVAL =>
// we already sent an identical channel_update not long ago (flapping protection in case we keep being disconnected/reconnected)
Expand All @@ -723,7 +726,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val
// if we have pending unsigned htlcs, then we cancel them and generate an update with the disabled flag set, that will be returned to the sender in a temporary channel failure
if (d.commitments.localChanges.proposed.collectFirst { case add: UpdateAddHtlc => add }.isDefined) {
log.debug("updating channel_update announcement (reason=disabled)")
val channelUpdate1 = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate(d), d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.capacity.toMilliSatoshi, isPrivate = !d.commitments.announceChannel, enable = false)
val channelUpdate1 = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate(d), d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.maxHtlcAmount, isPrivate = !d.commitments.announceChannel, enable = false)
// NB: the htlcs stay() in the commitments.localChange, they will be cleaned up after reconnection
d.commitments.localChanges.proposed.collect {
case add: UpdateAddHtlc => relayer ! RES_ADD_SETTLED(d.commitments.originChannels(add.id), add, HtlcResult.DisconnectedBeforeSigned(channelUpdate1))
Expand Down Expand Up @@ -1865,7 +1868,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val
if (d.channelUpdate.channelFlags.isEnabled) {
// if the channel isn't disabled we generate a new channel_update
log.info("updating channel_update announcement (reason=disabled)")
val channelUpdate1 = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate(d), d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.capacity.toMilliSatoshi, isPrivate = !d.commitments.announceChannel, enable = false)
val channelUpdate1 = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate(d), d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.maxHtlcAmount, isPrivate = !d.commitments.announceChannel, enable = false)
// then we update the state and replay the request
self forward c
// we use goto() to fire transitions
Expand All @@ -1878,7 +1881,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val
}

private def handleUpdateRelayFeeDisconnected(c: CMD_UPDATE_RELAY_FEE, d: DATA_NORMAL) = {
val channelUpdate1 = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate(d), c.cltvExpiryDelta_opt.getOrElse(d.channelUpdate.cltvExpiryDelta), d.channelUpdate.htlcMinimumMsat, c.feeBase, c.feeProportionalMillionths, d.commitments.capacity.toMilliSatoshi, isPrivate = !d.commitments.announceChannel, enable = false)
val channelUpdate1 = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate(d), c.cltvExpiryDelta_opt.getOrElse(d.channelUpdate.cltvExpiryDelta), d.channelUpdate.htlcMinimumMsat, c.feeBase, c.feeProportionalMillionths, d.commitments.maxHtlcAmount, isPrivate = !d.commitments.announceChannel, enable = false)
log.info(s"updating relay fees: prev={} next={}", d.channelUpdate.toStringShort, channelUpdate1.toStringShort)
val replyTo = if (c.replyTo == ActorRef.noSender) sender() else c.replyTo
replyTo ! RES_SUCCESS(c, d.channelId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import fr.acinq.eclair.channel.fsm.Channel._
import fr.acinq.eclair.channel.publish.TxPublisher.SetChannelId
import fr.acinq.eclair.transactions.Scripts
import fr.acinq.eclair.wire.protocol._
import fr.acinq.eclair.{Features, RealShortChannelId, ToMilliSatoshiConversion}
import fr.acinq.eclair.{Features, RealShortChannelId, ToMilliSatoshiConversion, UInt64}

/**
* Created by t-bast on 19/04/2022.
Expand Down Expand Up @@ -123,7 +123,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers {
commitmentFeerate = input.commitTxFeerate,
fundingAmount = input.fundingAmount,
dustLimit = input.localParams.dustLimit,
maxHtlcValueInFlightMsat = input.localParams.maxHtlcValueInFlightMsat,
maxHtlcValueInFlightMsat = UInt64(input.localParams.maxHtlcValueInFlightMsat.toLong),
htlcMinimum = input.localParams.htlcMinimum,
toSelfDelay = input.localParams.toSelfDelay,
maxAcceptedHtlcs = input.localParams.maxAcceptedHtlcs,
Expand Down Expand Up @@ -165,7 +165,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers {
temporaryChannelId = open.temporaryChannelId,
fundingAmount = d.init.fundingContribution_opt.getOrElse(0 sat),
dustLimit = localParams.dustLimit,
maxHtlcValueInFlightMsat = localParams.maxHtlcValueInFlightMsat,
maxHtlcValueInFlightMsat = UInt64(localParams.maxHtlcValueInFlightMsat.toLong),
htlcMinimum = localParams.htlcMinimum,
minimumDepth = minimumDepth.getOrElse(0),
toSelfDelay = localParams.toSelfDelay,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import fr.acinq.eclair.crypto.ShaChain
import fr.acinq.eclair.transactions.Transactions.TxOwner
import fr.acinq.eclair.transactions.{Scripts, Transactions}
import fr.acinq.eclair.wire.protocol.{AcceptChannel, AnnouncementSignatures, ChannelReady, ChannelTlv, Error, FundingCreated, FundingSigned, OpenChannel, TlvStream}
import fr.acinq.eclair.{Features, MilliSatoshiLong, RealShortChannelId, randomKey, toLongId}
import fr.acinq.eclair.{Features, MilliSatoshiLong, RealShortChannelId, UInt64, randomKey, toLongId}
import scodec.bits.ByteVector

import scala.util.{Failure, Success, Try}
Expand Down Expand Up @@ -87,7 +87,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers {
fundingSatoshis = input.fundingAmount,
pushMsat = input.pushAmount_opt.getOrElse(0 msat),
dustLimitSatoshis = input.localParams.dustLimit,
maxHtlcValueInFlightMsat = input.localParams.maxHtlcValueInFlightMsat,
maxHtlcValueInFlightMsat = UInt64(input.localParams.maxHtlcValueInFlightMsat.toLong),
channelReserveSatoshis = input.localParams.requestedChannelReserve_opt.get,
htlcMinimumMsat = input.localParams.htlcMinimum,
feeratePerKw = input.commitTxFeerate,
Expand Down Expand Up @@ -122,7 +122,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers {
val localShutdownScript = if (Features.canUseFeature(d.initFundee.localParams.initFeatures, d.initFundee.remoteInit.features, Features.UpfrontShutdownScript)) d.initFundee.localParams.defaultFinalScriptPubKey else ByteVector.empty
val accept = AcceptChannel(temporaryChannelId = open.temporaryChannelId,
dustLimitSatoshis = d.initFundee.localParams.dustLimit,
maxHtlcValueInFlightMsat = d.initFundee.localParams.maxHtlcValueInFlightMsat,
maxHtlcValueInFlightMsat = UInt64(d.initFundee.localParams.maxHtlcValueInFlightMsat.toLong),
channelReserveSatoshis = d.initFundee.localParams.requestedChannelReserve_opt.get,
minimumDepth = minimumDepth.getOrElse(0),
htlcMinimumMsat = d.initFundee.localParams.htlcMinimum,
Expand Down
Loading