Skip to content
Closed
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
11 changes: 5 additions & 6 deletions eclair-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,11 @@
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>com.softwaremill.quicklens</groupId>
<artifactId>quicklens_${scala.version.short}</artifactId>
<version>1.5.0</version>
</dependency>
<!-- MONITORING -->
<dependency>
<groupId>io.kamon</groupId>
Expand All @@ -264,12 +269,6 @@
<version>${kamon.version}</version>
</dependency>
<!-- TESTS -->
<dependency>
<groupId>com.softwaremill.quicklens</groupId>
<artifactId>quicklens_${scala.version.short}</artifactId>
<version>1.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-testkit_${scala.version.short}</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ package fr.acinq.eclair.blockchain.fee

import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin.Satoshi
import fr.acinq.eclair.Features
import fr.acinq.eclair.blockchain.CurrentFeerates
import fr.acinq.eclair.channel.ChannelVersion
import fr.acinq.eclair.channel.ChannelType

trait FeeEstimator {
// @formatter:off
Expand All @@ -32,13 +33,13 @@ case class FeeTargets(fundingBlockTarget: Int, commitmentBlockTarget: Int, mutua

case class FeerateTolerance(ratioLow: Double, ratioHigh: Double, anchorOutputMaxCommitFeerate: FeeratePerKw) {
/**
* @param channelVersion channel version
* @param channelType channel type
* @param networkFeerate reference fee rate (value we estimate from our view of the network)
* @param proposedFeerate fee rate proposed (new proposal through update_fee or previous proposal used in our current commit tx)
* @return true if the difference between proposed and reference fee rates is too high.
*/
def isFeeDiffTooHigh(channelVersion: ChannelVersion, networkFeerate: FeeratePerKw, proposedFeerate: FeeratePerKw): Boolean = {
if (channelVersion.hasAnchorOutputs) {
def isFeeDiffTooHigh(channelType: ChannelType, networkFeerate: FeeratePerKw, proposedFeerate: FeeratePerKw): Boolean = {
if (channelType.features.hasFeature(Features.AnchorOutputs)) {
proposedFeerate < networkFeerate * ratioLow || anchorOutputMaxCommitFeerate * ratioHigh < proposedFeerate
} else {
proposedFeerate < networkFeerate * ratioLow || networkFeerate * ratioHigh < proposedFeerate
Expand All @@ -60,15 +61,15 @@ case class OnChainFeeConf(feeTargets: FeeTargets, feeEstimator: FeeEstimator, cl
* - otherwise we use a feerate that should get the commit tx confirmed within the configured block target
*
* @param remoteNodeId nodeId of our channel peer
* @param channelVersion channel version
* @param channelType channel type
* @param currentFeerates_opt if provided, will be used to compute the most up-to-date network fee, otherwise we rely on the fee estimator
*/
def getCommitmentFeerate(remoteNodeId: PublicKey, channelVersion: ChannelVersion, channelCapacity: Satoshi, currentFeerates_opt: Option[CurrentFeerates]): FeeratePerKw = {
def getCommitmentFeerate(remoteNodeId: PublicKey, channelType: ChannelType, channelCapacity: Satoshi, currentFeerates_opt: Option[CurrentFeerates]): FeeratePerKw = {
val networkFeerate = currentFeerates_opt match {
case Some(currentFeerates) => currentFeerates.feeratesPerKw.feePerBlock(feeTargets.commitmentBlockTarget)
case None => feeEstimator.getFeeratePerKw(feeTargets.commitmentBlockTarget)
}
if (channelVersion.hasAnchorOutputs) {
if (channelType.features.hasFeature(Features.AnchorOutputs)) {
networkFeerate.min(feerateToleranceFor(remoteNodeId).anchorOutputMaxCommitFeerate)
} else {
networkFeerate
Expand Down
80 changes: 40 additions & 40 deletions eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright 2021 ACINQ SAS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package fr.acinq.eclair.channel

import scodec.bits.{BitVector, ByteVector}

/**
* Created by t-bast on 24/06/2021.
*/

/**
* Internal configuration option impacting the channel's structure or behavior.
* This must be set when creating the channel and cannot be changed afterwards.
*/
trait ChannelConfigOption {
def supportBit: Int
}

case class ChannelConfigOptions(activated: Set[ChannelConfigOption]) {

def hasOption(option: ChannelConfigOption): Boolean = activated.contains(option)

def bytes: ByteVector = toByteVector

def toByteVector: ByteVector = {
val indices = activated.map(_.supportBit)
if (indices.isEmpty) {
ByteVector.empty
} else {
// NB: when converting from BitVector to ByteVector, scodec pads right instead of left, so we make sure we pad to bytes *before* setting bits.
var buffer = BitVector.fill(indices.max + 1)(high = false).bytes.bits
indices.foreach(i => buffer = buffer.set(i))
buffer.reverse.bytes
}
}

}

object ChannelConfigOptions {

def standard: ChannelConfigOptions = ChannelConfigOptions(activated = Set(FundingPubKeyBasedChannelKeyPath))

def apply(options: ChannelConfigOption*): ChannelConfigOptions = ChannelConfigOptions(Set.from(options))

def apply(bytes: ByteVector): ChannelConfigOptions = {
val activated: Set[ChannelConfigOption] = bytes.bits.toIndexedSeq.reverse.zipWithIndex.collect {
case (true, 0) => FundingPubKeyBasedChannelKeyPath
}.toSet
ChannelConfigOptions(activated)
}

/**
* If set, the channel's BIP32 key path will be deterministically derived from the funding public key.
* It makes it very easy to retrieve funds when channel data has been lost:
* - connect to your peer and use option_data_loss_protect to get them to publish their remote commit tx
* - retrieve the commit tx from the bitcoin network, extract your funding pubkey from its witness data
* - recompute your channel keys and spend your output
*/
case object FundingPubKeyBasedChannelKeyPath extends ChannelConfigOption {
override def supportBit = 0
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2021 ACINQ SAS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package fr.acinq.eclair.channel

import fr.acinq.eclair.FeatureSupport.Optional
import fr.acinq.eclair.Features
import fr.acinq.eclair.Features.{AnchorOutputs, StaticRemoteKey}
import fr.acinq.eclair.transactions.Transactions.{AnchorOutputsCommitmentFormat, CommitmentFormat, DefaultCommitmentFormat}

/**
* Created by t-bast on 24/06/2021.
*/

/**
* A channel type is a specific combination of Bolt 9 features, defined in the RFC (Bolt 2).
*/
case class ChannelType(features: Features) {

val commitmentFormat: CommitmentFormat = {
if (features.hasFeature(AnchorOutputs)) {
AnchorOutputsCommitmentFormat
} else {
DefaultCommitmentFormat
}
}

/**
* True if our main output in the remote commitment is directly sent (without any delay) to one of our wallet addresses.
*/
def paysDirectlyToWallet: Boolean = {
features.hasFeature(Features.StaticRemoteKey) && !features.hasFeature(Features.AnchorOutputs)
}

}

object ChannelTypes {

val standard = ChannelType(Features.empty)
val staticRemoteKey = ChannelType(Features(StaticRemoteKey -> Optional))
val anchorOutputs = ChannelType(Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional))

/**
* Pick the channel type that should be applied based on features alone (in case our peer doesn't support explicit channel type negotiation).
*/
def pickChannelType(localFeatures: Features, remoteFeatures: Features): ChannelType = {
if (Features.canUseFeature(localFeatures, remoteFeatures, AnchorOutputs)) {
anchorOutputs
} else if (Features.canUseFeature(localFeatures, remoteFeatures, StaticRemoteKey)) {
staticRemoteKey
} else {
standard
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import fr.acinq.eclair.transactions.CommitmentSpec
import fr.acinq.eclair.transactions.Transactions._
import fr.acinq.eclair.wire.protocol.{AcceptChannel, ChannelAnnouncement, ChannelReestablish, ChannelUpdate, ClosingSigned, FailureMessage, FundingCreated, FundingLocked, FundingSigned, Init, OnionRoutingPacket, OpenChannel, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFulfillHtlc}
import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Features, MilliSatoshi, ShortChannelId, UInt64}
import scodec.bits.{BitVector, ByteVector}
import scodec.bits.ByteVector

import java.util.UUID

Expand Down Expand Up @@ -87,8 +87,14 @@ case class INPUT_INIT_FUNDER(temporaryChannelId: ByteVector32,
remote: ActorRef,
remoteInit: Init,
channelFlags: Byte,
channelVersion: ChannelVersion)
case class INPUT_INIT_FUNDEE(temporaryChannelId: ByteVector32, localParams: LocalParams, remote: ActorRef, remoteInit: Init, channelVersion: ChannelVersion)
channelConfig: ChannelConfigOptions,
channelType: ChannelType)
case class INPUT_INIT_FUNDEE(temporaryChannelId: ByteVector32,
localParams: LocalParams,
remote: ActorRef,
remoteInit: Init,
channelConfig: ChannelConfigOptions,
channelType: ChannelType)
case object INPUT_CLOSE_COMPLETE_TIMEOUT // when requesting a mutual close, we wait for as much as this timeout, then unilateral close
case object INPUT_DISCONNECTED
case class INPUT_RECONNECTED(remote: ActorRef, localInit: Init, remoteInit: Init)
Expand Down Expand Up @@ -375,7 +381,8 @@ final case class DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId: ByteVector32
initialFeeratePerKw: FeeratePerKw,
initialRelayFees_opt: Option[(MilliSatoshi, Int)],
remoteFirstPerCommitmentPoint: PublicKey,
channelVersion: ChannelVersion,
channelConfig: ChannelConfigOptions,
channelType: ChannelType,
lastSent: OpenChannel) extends Data {
val channelId: ByteVector32 = temporaryChannelId
}
Expand All @@ -388,7 +395,8 @@ final case class DATA_WAIT_FOR_FUNDING_CREATED(temporaryChannelId: ByteVector32,
initialRelayFees_opt: Option[(MilliSatoshi, Int)],
remoteFirstPerCommitmentPoint: PublicKey,
channelFlags: Byte,
channelVersion: ChannelVersion,
channelConfig: ChannelConfigOptions,
channelType: ChannelType,
lastSent: AcceptChannel) extends Data {
val channelId: ByteVector32 = temporaryChannelId
}
Expand All @@ -402,7 +410,8 @@ final case class DATA_WAIT_FOR_FUNDING_SIGNED(channelId: ByteVector32,
localCommitTx: CommitTx,
remoteCommit: RemoteCommit,
channelFlags: Byte,
channelVersion: ChannelVersion,
channelConfig: ChannelConfigOptions,
channelType: ChannelType,
lastSent: FundingCreated) extends Data
final case class DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments: Commitments,
fundingTx: Option[Transaction],
Expand Down Expand Up @@ -445,8 +454,8 @@ final case class DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT(commitments: Com

/**
* @param features current connection features, or last features used if the channel is disconnected. Note that these
* features are updated at each reconnection and may be different from the ones that were used when the
* channel was created. See [[ChannelVersion]] for permanent features associated to a channel.
* features are updated at each reconnection and may be different from the channel permanent features
* used to select the [[ChannelType]].
*/
final case class LocalParams(nodeId: PublicKey,
fundingKeyPath: DeterministicWallet.KeyPath,
Expand Down Expand Up @@ -482,55 +491,4 @@ object ChannelFlags {
val AnnounceChannel = 0x01.toByte
val Empty = 0x00.toByte
}

case class ChannelVersion(bits: BitVector) {
import ChannelVersion._

require(bits.size == ChannelVersion.LENGTH_BITS, "channel version takes 4 bytes")

val commitmentFormat: CommitmentFormat = if (hasAnchorOutputs) {
AnchorOutputsCommitmentFormat
} else {
DefaultCommitmentFormat
}

def |(other: ChannelVersion) = ChannelVersion(bits | other.bits)
def &(other: ChannelVersion) = ChannelVersion(bits & other.bits)
def ^(other: ChannelVersion) = ChannelVersion(bits ^ other.bits)

def isSet(bit: Int): Boolean = bits.reverse.get(bit)

def hasPubkeyKeyPath: Boolean = isSet(USE_PUBKEY_KEYPATH_BIT)
def hasStaticRemotekey: Boolean = isSet(USE_STATIC_REMOTEKEY_BIT)
def hasAnchorOutputs: Boolean = isSet(USE_ANCHOR_OUTPUTS_BIT)
/** True if our main output in the remote commitment is directly sent (without any delay) to one of our wallet addresses. */
def paysDirectlyToWallet: Boolean = hasStaticRemotekey && !hasAnchorOutputs
}

object ChannelVersion {
import scodec.bits._

val LENGTH_BITS: Int = 4 * 8

private val USE_PUBKEY_KEYPATH_BIT = 0 // bit numbers start at 0
private val USE_STATIC_REMOTEKEY_BIT = 1
private val USE_ANCHOR_OUTPUTS_BIT = 2

def fromBit(bit: Int): ChannelVersion = ChannelVersion(BitVector.low(LENGTH_BITS).set(bit).reverse)

def pickChannelVersion(localFeatures: Features, remoteFeatures: Features): ChannelVersion = {
if (Features.canUseFeature(localFeatures, remoteFeatures, Features.AnchorOutputs)) {
ANCHOR_OUTPUTS
} else if (Features.canUseFeature(localFeatures, remoteFeatures, Features.StaticRemoteKey)) {
STATIC_REMOTEKEY
} else {
STANDARD
}
}

val ZEROES = ChannelVersion(bin"00000000000000000000000000000000")
val STANDARD = ZEROES | fromBit(USE_PUBKEY_KEYPATH_BIT)
val STATIC_REMOTEKEY = STANDARD | fromBit(USE_STATIC_REMOTEKEY_BIT) // PUBKEY_KEYPATH + STATIC_REMOTEKEY
val ANCHOR_OUTPUTS = STATIC_REMOTEKEY | fromBit(USE_ANCHOR_OUTPUTS_BIT) // PUBKEY_KEYPATH + STATIC_REMOTEKEY + ANCHOR_OUTPUTS
}
// @formatter:on
Loading