1
0
mirror of https://github.com/ACINQ/eclair.git synced 2024-11-20 10:39:19 +01:00

make max-feerate-mismatch configurable and set default value to 500% (#62)

* make max-feerate-mismatch configurable and set default value to 500%
there can be a significant gap between the fee rate estimated by different bitcoin
clients (see estimatesmartfee) so we must set a reasonably high value for the threshold
above which we consider htat local and remote fee rates are too different and close the
channel.

* use difference/average to compare feerate
it becomes symmetrical and easier to reason with, and also more forgiving.

* make "UpdateFee" minimum ratio configurable
This commit is contained in:
Fabrice Drouin 2017-04-11 16:45:03 +02:00 committed by Pierre-Marie Padiou
parent e2350ca8b1
commit fd56d35073
12 changed files with 61 additions and 41 deletions

View File

@ -38,6 +38,14 @@ eclair {
fee-base-msat = 546000
fee-proportional-millionth = 10
// maximum local vs remote feerate mismatch; 1.0 means 100%
// actual check is abs((local feerate - remote fee rate) / (local fee rate + remote fee rate)/2) > fee rate mismatch
max-feerate-mismatch = 1.5
// funder will send an UpdateFee message if the difference between current commitment fee and actual current network fee is greater
// than this ratio.
update-fee_min-diff-ratio = 0.1
router-broadcast-interval = 10 seconds // this should be 60 seconds on mainnet
router-validate-interval = 2 seconds // this should be high enough to have a decent level of parallelism

View File

@ -21,22 +21,6 @@ object Globals {
* The value is updated by the [[fr.acinq.eclair.blockchain.PeerWatcher]] and read by all actors, hence it needs to be thread-safe.
*/
val feeratePerKw = new AtomicLong(0)
object Constants {
/**
* Funder will send an UpdateFee message if the difference between current commitment fee and actual current network fee is greater
* than this ratio.
*/
val UPDATE_FEE_MIN_DIFF_RATIO = 0.1
/**
* Fundee will unilaterally close the channel if the difference between the feeratePerKw proposed by the funder in an UpdateFee and
* actual current network fee is greater than this ratio.
*/
val UPDATE_FEE_MAX_DIFF_RATIO = 0.3
}
}

View File

@ -43,7 +43,9 @@ case class NodeParams(extendedPrivateKey: ExtendedPrivateKey,
announcementsDb: SimpleTypedDb[String, LightningMessage],
routerBroadcastInterval: FiniteDuration,
routerValidateInterval: FiniteDuration,
pingInterval: FiniteDuration)
pingInterval: FiniteDuration,
maxFeerateMismatch: Double,
updateFeeMinDiffRatio: Double)
object NodeParams {
@ -102,6 +104,8 @@ object NodeParams {
announcementsDb = Dbs.makeAnnouncementDb(db),
routerBroadcastInterval = FiniteDuration(config.getDuration("router-broadcast-interval").getSeconds, TimeUnit.SECONDS),
routerValidateInterval = FiniteDuration(config.getDuration("router-validate-interval").getSeconds, TimeUnit.SECONDS),
pingInterval = FiniteDuration(config.getDuration("ping-interval").getSeconds, TimeUnit.SECONDS))
pingInterval = FiniteDuration(config.getDuration("ping-interval").getSeconds, TimeUnit.SECONDS),
maxFeerateMismatch = config.getDouble("max-feerate-mismatch"),
updateFeeMinDiffRatio = config.getDouble("update-fee_min-diff-ratio"))
}
}

View File

@ -597,10 +597,10 @@ class Channel(val nodeParams: NodeParams, remoteNodeId: PublicKey, blockchain: A
case Event(CurrentFeerate(feeratePerKw), d: DATA_NORMAL) =>
d.commitments.localParams.isFunder match {
case true if Helpers.shouldUpdateFee(d.commitments.localCommit.spec.feeratePerKw, feeratePerKw) =>
case true if Helpers.shouldUpdateFee(d.commitments.localCommit.spec.feeratePerKw, feeratePerKw, nodeParams.updateFeeMinDiffRatio) =>
self ! CMD_UPDATE_FEE(feeratePerKw, commit = true)
stay
case false if Helpers.isFeeDiffTooHigh(d.commitments.localCommit.spec.feeratePerKw, feeratePerKw) =>
case false if Helpers.isFeeDiffTooHigh(d.commitments.localCommit.spec.feeratePerKw, feeratePerKw, nodeParams.maxFeerateMismatch) =>
handleLocalError(new RuntimeException(s"local/remote feerates are too different: remoteFeeratePerKw=${d.commitments.localCommit.spec.feeratePerKw} localFeeratePerKw=$feeratePerKw"), d)
case _ => stay
}
@ -791,10 +791,10 @@ class Channel(val nodeParams: NodeParams, remoteNodeId: PublicKey, blockchain: A
case Event(CurrentFeerate(feeratePerKw), d: DATA_SHUTDOWN) =>
d.commitments.localParams.isFunder match {
case true if Helpers.shouldUpdateFee(d.commitments.localCommit.spec.feeratePerKw, feeratePerKw) =>
case true if Helpers.shouldUpdateFee(d.commitments.localCommit.spec.feeratePerKw, feeratePerKw, nodeParams.updateFeeMinDiffRatio) =>
self ! CMD_UPDATE_FEE(feeratePerKw, commit = true)
stay
case false if Helpers.isFeeDiffTooHigh(d.commitments.localCommit.spec.feeratePerKw, feeratePerKw) =>
case false if Helpers.isFeeDiffTooHigh(d.commitments.localCommit.spec.feeratePerKw, feeratePerKw, nodeParams.maxFeerateMismatch) =>
handleLocalError(new RuntimeException(s"local/remote feerates are too different: remoteFeeratePerKw=${d.commitments.localCommit.spec.feeratePerKw} localFeeratePerKw=$feeratePerKw"), d)
case _ => stay
}

View File

@ -167,7 +167,8 @@ final case class LocalParams(nodeId: PublicKey,
shaSeed: BinaryData,
isFunder: Boolean,
globalFeatures: BinaryData,
localFeatures: BinaryData)
localFeatures: BinaryData,
maxFeerateMismatch: Double)
final case class RemoteParams(nodeId: PublicKey,
dustLimitSatoshis: Long,

View File

@ -290,7 +290,7 @@ object Commitments extends Logging {
}
val localFeeratePerKw = Globals.feeratePerKw.get()
if (Helpers.isFeeDiffTooHigh(fee.feeratePerKw, localFeeratePerKw)) {
if (Helpers.isFeeDiffTooHigh(fee.feeratePerKw, localFeeratePerKw, commitments.localParams.maxFeerateMismatch)) {
throw new RuntimeException(s"local/remote feerates are too different: remoteFeeratePerKw=${fee.feeratePerKw} localFeeratePerKw=$localFeeratePerKw")
}

View File

@ -3,7 +3,6 @@ package fr.acinq.eclair.channel
import fr.acinq.bitcoin.Crypto.{Point, PublicKey, Scalar, sha256}
import fr.acinq.bitcoin.Script._
import fr.acinq.bitcoin.{OutPoint, _}
import fr.acinq.eclair.Globals.Constants.{UPDATE_FEE_MAX_DIFF_RATIO, UPDATE_FEE_MIN_DIFF_RATIO}
import fr.acinq.eclair.crypto.Generators
import fr.acinq.eclair.transactions.Scripts._
import fr.acinq.eclair.transactions.Transactions._
@ -65,13 +64,31 @@ object Helpers {
}
}
def shouldUpdateFee(commitmentFeeratePerKw: Long, networkFeeratePerKw: Long): Boolean =
// negative feerate can happen in regtest mode
networkFeeratePerKw > 0 && Math.abs((networkFeeratePerKw - commitmentFeeratePerKw) / commitmentFeeratePerKw.toDouble) > UPDATE_FEE_MIN_DIFF_RATIO
/**
*
* @param remoteFeeratePerKw remote fee rate per kiloweight
* @param localFeeratePerKw local fee rate per kiloweight
* @return the "normalized" difference between local and remote fee rate, i.e. |remote - local| / avg(local, remote)
*/
def feeRateMismatch(remoteFeeratePerKw: Long, localFeeratePerKw: Long): Double =
Math.abs((2.0 * (remoteFeeratePerKw - localFeeratePerKw)) / (localFeeratePerKw + remoteFeeratePerKw))
def isFeeDiffTooHigh(remoteFeeratePerKw: Long, localFeeratePerKw: Long): Boolean =
def shouldUpdateFee(commitmentFeeratePerKw: Long, networkFeeratePerKw: Long, updateFeeMinDiffRatio: Double): Boolean =
// negative feerate can happen in regtest mode
remoteFeeratePerKw > 0 && Math.abs((remoteFeeratePerKw - localFeeratePerKw) / localFeeratePerKw.toDouble) > UPDATE_FEE_MAX_DIFF_RATIO
networkFeeratePerKw > 0 && feeRateMismatch(networkFeeratePerKw, commitmentFeeratePerKw) > updateFeeMinDiffRatio
/**
*
* @param remoteFeeratePerKw remote fee rate per kiloweight
* @param localFeeratePerKw local fee rate per kiloweight
* @param maxFeerateMismatchRatio maximum fee rate mismatch ratio
* @return true if the difference between local and remote fee rates is too high.
* the actual check is |remote - local| / avg(local, remote) > mismatch ratio
*/
def isFeeDiffTooHigh(remoteFeeratePerKw: Long, localFeeratePerKw: Long, maxFeerateMismatchRatio: Double): Boolean = {
// negative feerate can happen in regtest mode
remoteFeeratePerKw > 0 && feeRateMismatch(remoteFeeratePerKw, localFeeratePerKw) > maxFeerateMismatchRatio
}
object Funding {
@ -103,7 +120,7 @@ object Helpers {
if (!localParams.isFunder) {
// they are funder, we need to make sure that they can pay the fee is reasonable, and that they can afford to pay it
val localFeeratePerKw = Globals.feeratePerKw.get()
if (isFeeDiffTooHigh(initialFeeratePerKw, localFeeratePerKw)) {
if (isFeeDiffTooHigh(initialFeeratePerKw, localFeeratePerKw, localParams.maxFeerateMismatch)) {
throw new RuntimeException(s"local/remote feerates are too different: remoteFeeratePerKw=$initialFeeratePerKw localFeeratePerKw=$localFeeratePerKw")
}
val toRemote = MilliSatoshi(remoteSpec.toLocalMsat)

View File

@ -257,7 +257,8 @@ object Peer {
shaSeed = Crypto.sha256(generateKey(nodeParams, keyIndex :: 4L :: Nil).toBin), // TODO: check that
isFunder = isFunder,
globalFeatures = nodeParams.globalFeatures,
localFeatures = nodeParams.localFeatures)
localFeatures = nodeParams.localFeatures,
maxFeerateMismatch = nodeParams.maxFeerateMismatch)
}
def randomTemporaryChannelId: BinaryData = {

View File

@ -46,7 +46,9 @@ object TestConstants {
announcementsDb = Dbs.makeAnnouncementDb(db),
routerBroadcastInterval = 60 seconds,
routerValidateInterval = 2 seconds,
pingInterval = 30 seconds)
pingInterval = 30 seconds,
maxFeerateMismatch = 1.5,
updateFeeMinDiffRatio = 0.1)
val id = nodeParams.privateKey.publicKey
val channelParams = Peer.makeChannelParams(
nodeParams = nodeParams,
@ -87,7 +89,9 @@ object TestConstants {
announcementsDb = Dbs.makeAnnouncementDb(db),
routerBroadcastInterval = 60 seconds,
routerValidateInterval = 2 seconds,
pingInterval = 30 seconds)
pingInterval = 30 seconds,
maxFeerateMismatch = 1.0,
updateFeeMinDiffRatio = 0.1)
val id = nodeParams.privateKey.publicKey
val channelParams = Peer.makeChannelParams(
nodeParams = nodeParams,

View File

@ -950,9 +950,9 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
within(30 seconds) {
val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
val sender = TestProbe()
sender.send(bob, UpdateFee("00" * 32, 50000))
sender.send(bob, UpdateFee("00" * 32, 85000))
val error = bob2alice.expectMsgType[Error]
assert(new String(error.data) === "local/remote feerates are too different: remoteFeeratePerKw=50000 localFeeratePerKw=10000")
assert(new String(error.data) === "local/remote feerates are too different: remoteFeeratePerKw=85000 localFeeratePerKw=10000")
awaitCond(bob.stateName == CLOSING)
bob2blockchain.expectMsg(PublishAsap(tx))
bob2blockchain.expectMsgType[WatchConfirmed]
@ -1169,7 +1169,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
test("recv CurrentFeerate (when fundee, commit-fee/network-fee are very different)") { case (_, bob, _, bob2alice, _, bob2blockchain, _) =>
within(30 seconds) {
val sender = TestProbe()
val event = CurrentFeerate(20000)
val event = CurrentFeerate(100)
sender.send(bob, event)
bob2alice.expectMsgType[Error]
bob2blockchain.expectMsgType[PublishAsap]

View File

@ -439,9 +439,9 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
within(30 seconds) {
val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx
val sender = TestProbe()
sender.send(bob, UpdateFee("00" * 32, 50000))
sender.send(bob, UpdateFee("00" * 32, 65000))
val error = bob2alice.expectMsgType[Error]
assert(new String(error.data) === "local/remote feerates are too different: remoteFeeratePerKw=50000 localFeeratePerKw=10000")
assert(new String(error.data) === "local/remote feerates are too different: remoteFeeratePerKw=65000 localFeeratePerKw=10000")
awaitCond(bob.stateName == CLOSING)
bob2blockchain.expectMsg(PublishAsap(tx))
bob2blockchain.expectMsgType[WatchConfirmed]
@ -502,7 +502,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
test("recv CurrentFeerate (when fundee, commit-fee/network-fee are very different)") { case (_, bob, _, bob2alice, _, bob2blockchain) =>
within(30 seconds) {
val sender = TestProbe()
val event = CurrentFeerate(20000)
val event = CurrentFeerate(1000)
sender.send(bob, event)
bob2alice.expectMsgType[Error]
bob2blockchain.expectMsgType[PublishAsap]

View File

@ -46,7 +46,8 @@ object ChannelStateSpec {
shaSeed = BinaryData("05" * 32),
isFunder = true,
globalFeatures = "foo".getBytes(),
localFeatures = "bar".getBytes())
localFeatures = "bar".getBytes(),
maxFeerateMismatch = 5.0)
val remoteParams = RemoteParams(
nodeId = randomKey.publicKey,