From 4293514700aab4175234023b13ed89ae21c87878 Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 19 Jul 2019 17:52:07 +0200 Subject: [PATCH 01/25] Use feeEstimator in NodeParams with custom confirmationTarget --- .../scala/fr/acinq/eclair/NodeParams.scala | 6 +++ .../eclair/blockchain/fee/FeeEstimator.scala | 8 ++++ .../eclair/blockchain/fee/FeeProvider.scala | 18 +++++++++ .../fr/acinq/eclair/channel/Channel.scala | 38 +++++++++---------- .../fr/acinq/eclair/channel/Commitments.scala | 5 ++- .../fr/acinq/eclair/channel/Helpers.scala | 35 ++++++++--------- .../main/scala/fr/acinq/eclair/io/Peer.scala | 4 +- .../scala/fr/acinq/eclair/TestConstants.scala | 15 +++++++- .../fr/acinq/eclair/TestkitBaseClass.scala | 2 - .../states/StateTestsHelperMethods.scala | 17 ++++----- .../channel/states/e/NormalStateSpec.scala | 4 +- .../channel/states/f/ShutdownStateSpec.scala | 9 +++-- .../states/g/NegotiatingStateSpec.scala | 10 ++--- .../channel/states/h/ClosingStateSpec.scala | 12 +++--- .../interop/rustytests/RustyTestsSpec.scala | 12 +++--- 15 files changed, 120 insertions(+), 75 deletions(-) create mode 100644 eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala index 9266d701e7..d8e8fd2480 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala @@ -25,6 +25,7 @@ import com.typesafe.config.{Config, ConfigFactory} import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.{Block, ByteVector32} import fr.acinq.eclair.NodeParams.WatcherType +import fr.acinq.eclair.blockchain.fee.FeeEstimator import fr.acinq.eclair.channel.Channel import fr.acinq.eclair.crypto.KeyManager import fr.acinq.eclair.db._ @@ -55,6 +56,7 @@ case class NodeParams(keyManager: KeyManager, maxToLocalDelayBlocks: Int, minDepthBlocks: Int, smartfeeNBlocks: Int, + feeEstimator: FeeEstimator, feeBaseMsat: Int, feeProportionalMillionth: Int, reserveToFundingRatio: Double, @@ -193,6 +195,10 @@ object NodeParams { maxToLocalDelayBlocks = config.getInt("max-to-local-delay-blocks"), minDepthBlocks = config.getInt("mindepth-blocks"), smartfeeNBlocks = 3, + feeEstimator = new FeeEstimator { + override def getFeeratePerKb(target: Int): Long = Globals.feeratesPerKB.get().feePerBlock(target) + override def getFeeratePerKw(target: Int): Long = Globals.feeratesPerKw.get().feePerBlock(target) + }, feeBaseMsat = config.getInt("fee-base-msat"), feeProportionalMillionth = config.getInt("fee-proportional-millionths"), reserveToFundingRatio = config.getDouble("reserve-to-funding-ratio"), diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala new file mode 100644 index 0000000000..05f76d4a05 --- /dev/null +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala @@ -0,0 +1,8 @@ +package fr.acinq.eclair.blockchain.fee + +trait FeeEstimator { + + def getFeeratePerKb(target: Int) : Long + def getFeeratePerKw(target: Int) : Long + +} \ No newline at end of file diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeProvider.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeProvider.scala index 050b7f0a26..1db895c885 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeProvider.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeProvider.scala @@ -32,11 +32,29 @@ trait FeeProvider { // stores fee rate in satoshi/kb (1 kb = 1000 bytes) case class FeeratesPerKB(block_1: Long, blocks_2: Long, blocks_6: Long, blocks_12: Long, blocks_36: Long, blocks_72: Long) { require(block_1 > 0 && blocks_2 > 0 && blocks_6 > 0 && blocks_12 > 0 && blocks_36 > 0 && blocks_72 > 0, "all feerates must be strictly greater than 0") + + def feePerBlock(target: Int) = target match { + case 1 => block_1 + case 2 => blocks_2 + case t if t <= 6 => blocks_6 + case t if t <= 12 => blocks_12 + case t if t <= 36 => blocks_36 + case _ => blocks_72 + } } // stores fee rate in satoshi/kw (1 kw = 1000 weight units) case class FeeratesPerKw(block_1: Long, blocks_2: Long, blocks_6: Long, blocks_12: Long, blocks_36: Long, blocks_72: Long) { require(block_1 > 0 && blocks_2 > 0 && blocks_6 > 0 && blocks_12 > 0 && blocks_36 > 0 && blocks_72 > 0, "all feerates must be strictly greater than 0") + + def feePerBlock(target: Int) = target match { + case 1 => block_1 + case 2 => blocks_2 + case t if t <= 6 => blocks_6 + case t if t <= 12 => blocks_12 + case t if t <= 36 => blocks_36 + case _ => blocks_72 + } } object FeeratesPerKw { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 3199abcbe4..1afb807743 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -681,7 +681,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId } case Event(fee: UpdateFee, d: DATA_NORMAL) => - Try(Commitments.receiveFee(d.commitments, fee, nodeParams.maxFeerateMismatch)) match { + Try(Commitments.receiveFee(d.commitments, fee, nodeParams.maxFeerateMismatch, nodeParams.feeEstimator)) match { case Success(commitments1) => stay using d.copy(commitments = commitments1) case Failure(cause) => handleLocalError(cause, d, Some(fee)) } @@ -836,7 +836,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId // there are no pending signed htlcs, let's go directly to NEGOTIATING if (d.commitments.localParams.isFunder) { // we are funder, need to initiate the negotiation by sending the first closing_signed - val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, d.commitments, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey) + val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, d.commitments, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey, nodeParams.feeEstimator) goto(NEGOTIATING) using store(DATA_NEGOTIATING(d.commitments, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending sendList :+ closingSigned } else { // we are fundee, will wait for their closing_signed @@ -853,7 +853,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId handleLocalError(HtlcTimedout(d.channelId, d.commitments.timedoutOutgoingHtlcs(count)), d, Some(c)) case Event(c@CurrentFeerates(feeratesPerKw), d: DATA_NORMAL) => - val networkFeeratePerKw = feeratesPerKw.blocks_2 + val networkFeeratePerKw = feeratesPerKw.feePerBlock(target = 2) d.commitments.localParams.isFunder match { case true if Helpers.shouldUpdateFee(d.commitments.localCommit.spec.feeratePerKw, networkFeeratePerKw, nodeParams.updateFeeMinDiffRatio) => self ! CMD_UPDATE_FEE(networkFeeratePerKw, commit = true) @@ -1040,7 +1040,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId } case Event(fee: UpdateFee, d: DATA_SHUTDOWN) => - Try(Commitments.receiveFee(d.commitments, fee, nodeParams.maxFeerateMismatch)) match { + Try(Commitments.receiveFee(d.commitments, fee, nodeParams.maxFeerateMismatch, nodeParams.feeEstimator)) match { case Success(commitments1) => stay using d.copy(commitments = commitments1) case Failure(cause) => handleLocalError(cause, d, Some(fee)) } @@ -1079,7 +1079,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId if (commitments1.hasNoPendingHtlcs) { if (d.commitments.localParams.isFunder) { // we are funder, need to initiate the negotiation by sending the first closing_signed - val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey) + val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey, nodeParams.feeEstimator) goto(NEGOTIATING) using store(DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending revocation :: closingSigned :: Nil } else { // we are fundee, will wait for their closing_signed @@ -1115,7 +1115,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId log.debug(s"switching to NEGOTIATING spec:\n${Commitments.specs2String(commitments1)}") if (d.commitments.localParams.isFunder) { // we are funder, need to initiate the negotiation by sending the first closing_signed - val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey) + val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey, nodeParams.feeEstimator) goto(NEGOTIATING) using store(DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending closingSigned } else { // we are fundee, will wait for their closing_signed @@ -1136,7 +1136,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId handleLocalError(HtlcTimedout(d.channelId, d.commitments.timedoutOutgoingHtlcs(count)), d, Some(c)) case Event(c@CurrentFeerates(feerates), d: DATA_SHUTDOWN) => - val networkFeeratePerKw = feerates.blocks_2 + val networkFeeratePerKw = feerates.feePerBlock(target = 2) d.commitments.localParams.isFunder match { case true if Helpers.shouldUpdateFee(d.commitments.localCommit.spec.feeratePerKw, networkFeeratePerKw, nodeParams.updateFeeMinDiffRatio) => self ! CMD_UPDATE_FEE(networkFeeratePerKw, commit = true) @@ -1169,7 +1169,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId // if we are fundee and we were waiting for them to send their first closing_signed, we don't have a lastLocalClosingFee, so we compute a firstClosingFee val lastLocalClosingFee = d.closingTxProposed.last.lastOption.map(_.localClosingSigned.feeSatoshis).map(Satoshi) val nextClosingFee = Closing.nextClosingFee( - localClosingFee = lastLocalClosingFee.getOrElse(Closing.firstClosingFee(d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey)), + localClosingFee = lastLocalClosingFee.getOrElse(Closing.firstClosingFee(d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.feeEstimator)), remoteClosingFee = Satoshi(remoteClosingFee)) val (closingTx, closingSigned) = Closing.makeClosingTx(keyManager, d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nextClosingFee) if (Some(nextClosingFee) == lastLocalClosingFee) { @@ -1214,19 +1214,19 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId log.info(s"got valid payment preimage, recalculating transactions to redeem the corresponding htlc on-chain") val localCommitPublished1 = d.localCommitPublished.map { case localCommitPublished => - val localCommitPublished1 = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, commitments1, localCommitPublished.commitTx) + val localCommitPublished1 = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, commitments1, localCommitPublished.commitTx, nodeParams.feeEstimator) doPublish(localCommitPublished1) localCommitPublished1 } val remoteCommitPublished1 = d.remoteCommitPublished.map { case remoteCommitPublished => - val remoteCommitPublished1 = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, commitments1, commitments1.remoteCommit, remoteCommitPublished.commitTx) + val remoteCommitPublished1 = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, commitments1, commitments1.remoteCommit, remoteCommitPublished.commitTx, nodeParams.feeEstimator) doPublish(remoteCommitPublished1) remoteCommitPublished1 } val nextRemoteCommitPublished1 = d.nextRemoteCommitPublished.map { case remoteCommitPublished => - val remoteCommitPublished1 = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, commitments1, commitments1.remoteCommit, remoteCommitPublished.commitTx) + val remoteCommitPublished1 = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, commitments1, commitments1.remoteCommit, remoteCommitPublished.commitTx, nodeParams.feeEstimator) doPublish(remoteCommitPublished1) remoteCommitPublished1 } @@ -1291,7 +1291,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId } } val revokedCommitPublished1 = d.revokedCommitPublished.map { rev => - val (rev1, tx_opt) = Closing.claimRevokedHtlcTxOutputs(keyManager, d.commitments, rev, tx) + val (rev1, tx_opt) = Closing.claimRevokedHtlcTxOutputs(keyManager, d.commitments, rev, tx, nodeParams.feeEstimator) tx_opt.foreach(claimTx => blockchain ! PublishAsap(claimTx)) tx_opt.foreach(claimTx => blockchain ! WatchSpent(self, tx, claimTx.txIn.head.outPoint.index.toInt, BITCOIN_OUTPUT_SPENT)) rev1 @@ -1551,7 +1551,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId // note: in any case we still need to keep all previously sent closing_signed, because they may publish one of them if (d.commitments.localParams.isFunder) { // we could use the last closing_signed we sent, but network fees may have changed while we were offline so it is better to restart from scratch - val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey) + val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.feeEstimator) val closingTxProposed1 = d.closingTxProposed :+ List(ClosingTxProposed(closingTx.tx, closingSigned)) goto(NEGOTIATING) using store(d.copy(closingTxProposed = closingTxProposed1)) sending d.localShutdown :: closingSigned :: Nil } else { @@ -1906,7 +1906,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId } else { val commitTx = d.commitments.localCommit.publishableTxs.commitTx.tx - val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, d.commitments, commitTx) + val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, d.commitments, commitTx, nodeParams.feeEstimator) doPublish(localCommitPublished) val nextData = d match { @@ -1981,7 +1981,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId log.warning(s"they published their current commit in txid=${commitTx.txid}") require(commitTx.txid == d.commitments.remoteCommit.txid, "txid mismatch") - val remoteCommitPublished = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, d.commitments, d.commitments.remoteCommit, commitTx) + val remoteCommitPublished = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, d.commitments, d.commitments.remoteCommit, commitTx, nodeParams.feeEstimator) doPublish(remoteCommitPublished) val nextData = d match { @@ -1998,7 +1998,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId log.warning(s"they published their future commit (because we asked them to) in txid=${commitTx.txid}") // if we are in this state, then this field is defined val remotePerCommitmentPoint = d.remoteChannelReestablish.myCurrentPerCommitmentPoint.get - val remoteCommitPublished = Helpers.Closing.claimRemoteCommitMainOutput(keyManager, d.commitments, remotePerCommitmentPoint, commitTx) + val remoteCommitPublished = Helpers.Closing.claimRemoteCommitMainOutput(keyManager, d.commitments, remotePerCommitmentPoint, commitTx, nodeParams.feeEstimator) val nextData = DATA_CLOSING(d.commitments, fundingTx = None, waitingSince = now, Nil, futureRemoteCommitPublished = Some(remoteCommitPublished)) doPublish(remoteCommitPublished) @@ -2011,7 +2011,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId val remoteCommit = d.commitments.remoteNextCommitInfo.left.get.nextRemoteCommit require(commitTx.txid == remoteCommit.txid, "txid mismatch") - val remoteCommitPublished = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, d.commitments, remoteCommit, commitTx) + val remoteCommitPublished = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, d.commitments, remoteCommit, commitTx, nodeParams.feeEstimator) doPublish(remoteCommitPublished) val nextData = d match { @@ -2044,7 +2044,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId def handleRemoteSpentOther(tx: Transaction, d: HasCommitments) = { log.warning(s"funding tx spent in txid=${tx.txid}") - Helpers.Closing.claimRevokedRemoteCommitTxOutputs(keyManager, d.commitments, tx, nodeParams.db.channels) match { + Helpers.Closing.claimRevokedRemoteCommitTxOutputs(keyManager, d.commitments, tx, nodeParams.db.channels, nodeParams.feeEstimator) match { case Some(revokedCommitPublished) => log.warning(s"txid=${tx.txid} was a revoked commitment, publishing the penalty tx") val exc = FundingTxSpent(d.channelId, tx) @@ -2091,7 +2091,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId // let's try to spend our current local tx val commitTx = d.commitments.localCommit.publishableTxs.commitTx.tx - val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, d.commitments, commitTx) + val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, d.commitments, commitTx, nodeParams.feeEstimator) doPublish(localCommitPublished) goto(ERR_INFORMATION_LEAK) sending error diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 579f8bac9f..cd329f8747 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -19,6 +19,7 @@ package fr.acinq.eclair.channel import akka.event.LoggingAdapter import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey, sha256} import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, Satoshi} +import fr.acinq.eclair.blockchain.fee.FeeEstimator import fr.acinq.eclair.crypto.{Generators, KeyManager, ShaChain, Sphinx} import fr.acinq.eclair.payment._ import fr.acinq.eclair.transactions.Transactions._ @@ -310,7 +311,7 @@ object Commitments { (commitments1, fee) } - def receiveFee(commitments: Commitments, fee: UpdateFee, maxFeerateMismatch: Double): Commitments = { + def receiveFee(commitments: Commitments, fee: UpdateFee, maxFeerateMismatch: Double, feeEstimator: FeeEstimator): Commitments = { if (commitments.localParams.isFunder) { throw FundeeCannotSendUpdateFee(commitments.channelId) } @@ -319,7 +320,7 @@ object Commitments { throw FeerateTooSmall(commitments.channelId, remoteFeeratePerKw = fee.feeratePerKw) } - val localFeeratePerKw = Globals.feeratesPerKw.get.blocks_2 + val localFeeratePerKw = feeEstimator.getFeeratePerKw(target = 2) if (Helpers.isFeeDiffTooHigh(fee.feeratePerKw, localFeeratePerKw, maxFeerateMismatch)) { throw FeerateTooDifferent(commitments.channelId, localFeeratePerKw = localFeeratePerKw, remoteFeeratePerKw = fee.feeratePerKw) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index f4609344f5..13b51a1bdf 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -21,6 +21,7 @@ import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey, ripemd160, sha256} import fr.acinq.bitcoin.Script._ import fr.acinq.bitcoin.{OutPoint, _} import fr.acinq.eclair.blockchain.EclairWallet +import fr.acinq.eclair.blockchain.fee.FeeEstimator import fr.acinq.eclair.channel.Channel.REFRESH_CHANNEL_UPDATE_INTERVAL import fr.acinq.eclair.crypto.{Generators, KeyManager} import fr.acinq.eclair.db.ChannelsDb @@ -111,7 +112,7 @@ object Helpers { throw ChannelReserveNotMet(open.temporaryChannelId, toLocalMsat, toRemoteMsat, open.channelReserveSatoshis) } - val localFeeratePerKw = Globals.feeratesPerKw.get.blocks_2 + val localFeeratePerKw = nodeParams.feeEstimator.getFeeratePerKw(target = 2) if (isFeeDiffTooHigh(open.feeratePerKw, localFeeratePerKw, nodeParams.maxFeerateMismatch)) throw FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.feeratePerKw) // only enforce dust limit check on mainnet if (nodeParams.chainHash == Block.LivenetGenesisBlock.hash) { @@ -423,21 +424,21 @@ object Helpers { } } - def firstClosingFee(commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector)(implicit log: LoggingAdapter): Satoshi = { + def firstClosingFee(commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feeEstimator: FeeEstimator)(implicit log: LoggingAdapter): Satoshi = { import commitments._ // this is just to estimate the weight, it depends on size of the pubkey scripts val dummyClosingTx = Transactions.makeClosingTx(commitInput, localScriptPubkey, remoteScriptPubkey, localParams.isFunder, Satoshi(0), Satoshi(0), localCommit.spec) val closingWeight = Transaction.weight(Transactions.addSigs(dummyClosingTx, dummyPublicKey, remoteParams.fundingPubKey, Transactions.PlaceHolderSig, Transactions.PlaceHolderSig).tx) // no need to use a very high fee here, so we target 6 blocks; also, we "MUST set fee_satoshis less than or equal to the base fee of the final commitment transaction" - val feeratePerKw = Math.min(Globals.feeratesPerKw.get.blocks_6, commitments.localCommit.spec.feeratePerKw) + val feeratePerKw = Math.min(feeEstimator.getFeeratePerKw(target = 6), commitments.localCommit.spec.feeratePerKw) log.info(s"using feeratePerKw=$feeratePerKw for initial closing tx") Transactions.weight2fee(feeratePerKw, closingWeight) } def nextClosingFee(localClosingFee: Satoshi, remoteClosingFee: Satoshi): Satoshi = ((localClosingFee + remoteClosingFee) / 4) * 2 - def makeFirstClosingTx(keyManager: KeyManager, commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector)(implicit log: LoggingAdapter): (ClosingTx, ClosingSigned) = { - val closingFee = firstClosingFee(commitments, localScriptPubkey, remoteScriptPubkey) + def makeFirstClosingTx(keyManager: KeyManager, commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feeEstimator: FeeEstimator)(implicit log: LoggingAdapter): (ClosingTx, ClosingSigned) = { + val closingFee = firstClosingFee(commitments, localScriptPubkey, remoteScriptPubkey, feeEstimator) makeClosingTx(keyManager, commitments, localScriptPubkey, remoteScriptPubkey, closingFee) } @@ -490,7 +491,7 @@ object Helpers { * @param commitments our commitment data, which include payment preimages * @return a list of transactions (one per HTLC that we can claim) */ - def claimCurrentLocalCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, tx: Transaction)(implicit log: LoggingAdapter): LocalCommitPublished = { + def claimCurrentLocalCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, tx: Transaction, feeEstimator: FeeEstimator)(implicit log: LoggingAdapter): LocalCommitPublished = { import commitments._ require(localCommit.publishableTxs.commitTx.tx.txid == tx.txid, "txid mismatch, provided tx is not the current local commit tx") @@ -499,7 +500,7 @@ object Helpers { val localDelayedPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) // no need to use a high fee rate for delayed transactions (we are the only one who can spend them) - val feeratePerKwDelayed = Globals.feeratesPerKw.get.blocks_6 + val feeratePerKwDelayed = feeEstimator.getFeeratePerKw(target = 6) // first we will claim our main output as soon as the delay is over val mainDelayedTx = generateTx("main-delayed-output")(Try { @@ -562,7 +563,7 @@ object Helpers { * @param tx the remote commitment transaction that has just been published * @return a list of transactions (one per HTLC that we can claim) */ - def claimRemoteCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, remoteCommit: RemoteCommit, tx: Transaction)(implicit log: LoggingAdapter): RemoteCommitPublished = { + def claimRemoteCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, remoteCommit: RemoteCommit, tx: Transaction, feeEstimator: FeeEstimator)(implicit log: LoggingAdapter): RemoteCommitPublished = { import commitments.{commitInput, localParams, remoteParams} require(remoteCommit.txid == tx.txid, "txid mismatch, provided tx is not the current remote commit tx") val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, remoteCommit.index, localParams, remoteParams, commitInput, remoteCommit.remotePerCommitmentPoint, remoteCommit.spec) @@ -574,7 +575,7 @@ object Helpers { val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(localParams.channelKeyPath).publicKey, remoteCommit.remotePerCommitmentPoint) // we need to use a rather high fee for htlc-claim because we compete with the counterparty - val feeratePerKwHtlc = Globals.feeratesPerKw.get.blocks_2 + val feeratePerKwHtlc = feeEstimator.getFeeratePerKw(target = 2) // those are the preimages to existing received htlcs val preimages = commitments.localChanges.all.collect { case u: UpdateFulfillHtlc => u.paymentPreimage } @@ -602,7 +603,7 @@ object Helpers { }) }.toSeq.flatten - claimRemoteCommitMainOutput(keyManager, commitments, remoteCommit.remotePerCommitmentPoint, tx).copy( + claimRemoteCommitMainOutput(keyManager, commitments, remoteCommit.remotePerCommitmentPoint, tx, feeEstimator).copy( claimHtlcSuccessTxs = txes.toList.collect { case c: ClaimHtlcSuccessTx => c.tx }, claimHtlcTimeoutTxs = txes.toList.collect { case c: ClaimHtlcTimeoutTx => c.tx } ) @@ -619,11 +620,11 @@ object Helpers { * @param tx the remote commitment transaction that has just been published * @return a list of transactions (one per HTLC that we can claim) */ - def claimRemoteCommitMainOutput(keyManager: KeyManager, commitments: Commitments, remotePerCommitmentPoint: PublicKey, tx: Transaction)(implicit log: LoggingAdapter): RemoteCommitPublished = { + def claimRemoteCommitMainOutput(keyManager: KeyManager, commitments: Commitments, remotePerCommitmentPoint: PublicKey, tx: Transaction, feeEstimator: FeeEstimator)(implicit log: LoggingAdapter): RemoteCommitPublished = { val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(commitments.localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) // no need to use a high fee rate for our main output (we are the only one who can spend it) - val feeratePerKwMain = Globals.feeratesPerKw.get.blocks_6 + val feeratePerKwMain = feeEstimator.getFeeratePerKw(target = 6) val mainTx = generateTx("claim-p2wpkh-output")(Try { val claimMain = Transactions.makeClaimP2WPKHOutputTx(tx, Satoshi(commitments.localParams.dustLimitSatoshis), @@ -650,7 +651,7 @@ object Helpers { * * @return a [[RevokedCommitPublished]] object containing penalty transactions if the tx is a revoked commitment */ - def claimRevokedRemoteCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, tx: Transaction, db: ChannelsDb)(implicit log: LoggingAdapter): Option[RevokedCommitPublished] = { + def claimRevokedRemoteCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, tx: Transaction, db: ChannelsDb, feeEstimator: FeeEstimator)(implicit log: LoggingAdapter): Option[RevokedCommitPublished] = { import commitments._ require(tx.txIn.size == 1, "commitment tx should have 1 input") val obscuredTxNumber = Transactions.decodeTxNumber(tx.txIn(0).sequence, tx.lockTime) @@ -670,9 +671,9 @@ object Helpers { val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint) // no need to use a high fee rate for our main output (we are the only one who can spend it) - val feeratePerKwMain = Globals.feeratesPerKw.get.blocks_6 + val feeratePerKwMain = feeEstimator.getFeeratePerKw(target = 6) // we need to use a high fee here for punishment txes because after a delay they can be spent by the counterparty - val feeratePerKwPenalty = Globals.feeratesPerKw.get.blocks_2 + val feeratePerKwPenalty = feeEstimator.getFeeratePerKw(target = 2) // first we will claim our main output right away val mainTx = generateTx("claim-p2wpkh-output")(Try { @@ -737,7 +738,7 @@ object Helpers { * @param htlcTx * @return */ - def claimRevokedHtlcTxOutputs(keyManager: KeyManager, commitments: Commitments, revokedCommitPublished: RevokedCommitPublished, htlcTx: Transaction)(implicit log: LoggingAdapter): (RevokedCommitPublished, Option[Transaction]) = { + def claimRevokedHtlcTxOutputs(keyManager: KeyManager, commitments: Commitments, revokedCommitPublished: RevokedCommitPublished, htlcTx: Transaction, feeEstimator: FeeEstimator)(implicit log: LoggingAdapter): (RevokedCommitPublished, Option[Transaction]) = { if (htlcTx.txIn.map(_.outPoint.txid).contains(revokedCommitPublished.commitTx.txid) && !(revokedCommitPublished.claimMainOutputTx ++ revokedCommitPublished.mainPenaltyTx ++ revokedCommitPublished.htlcPenaltyTxs).map(_.txid).toSet.contains(htlcTx.txid)) { log.info(s"looks like txid=${htlcTx.txid} could be a 2nd level htlc tx spending revoked commit txid=${revokedCommitPublished.commitTx.txid}") @@ -756,7 +757,7 @@ object Helpers { val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) // we need to use a high fee here for punishment txes because after a delay they can be spent by the counterparty - val feeratePerKwPenalty = Globals.feeratesPerKw.get.block_1 + val feeratePerKwPenalty = feeEstimator.getFeeratePerKw(target = 1) generateTx("claim-htlc-delayed-penalty")(Try { val htlcDelayedPenalty = Transactions.makeClaimDelayedOutputPenaltyTx(htlcTx, Satoshi(localParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwPenalty) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index 08969aed8d..eb4f8b2bf6 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -283,8 +283,8 @@ class Peer(val nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: A val (channel, localParams) = createNewChannel(nodeParams, funder = true, c.fundingSatoshis.toLong, origin_opt = Some(sender)) c.timeout_opt.map(openTimeout => context.system.scheduler.scheduleOnce(openTimeout.duration, channel, Channel.TickChannelOpenTimeout)(context.dispatcher)) val temporaryChannelId = randomBytes32 - val channelFeeratePerKw = Globals.feeratesPerKw.get.blocks_2 - val fundingTxFeeratePerKw = c.fundingTxFeeratePerKw_opt.getOrElse(Globals.feeratesPerKw.get.blocks_6) + val channelFeeratePerKw = nodeParams.feeEstimator.getFeeratePerKw(target = 2) + val fundingTxFeeratePerKw = c.fundingTxFeeratePerKw_opt.getOrElse(nodeParams.feeEstimator.getFeeratePerKw(target = 6)) log.info(s"requesting a new channel with fundingSatoshis=${c.fundingSatoshis}, pushMsat=${c.pushMsat} and fundingFeeratePerByte=${c.fundingTxFeeratePerKw_opt} temporaryChannelId=$temporaryChannelId localParams=$localParams") channel ! INPUT_INIT_FUNDER(temporaryChannelId, c.fundingSatoshis.amount, c.pushMsat.amount, channelFeeratePerKw, fundingTxFeeratePerKw, localParams, d.transport, d.remoteInit, c.channelFlags.getOrElse(nodeParams.channelFlags)) stay using d.copy(channels = d.channels + (TemporaryChannelId(temporaryChannelId) -> channel)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala index 79770b5833..ba67d011f6 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala @@ -17,10 +17,10 @@ package fr.acinq.eclair import java.sql.{Connection, DriverManager} - import fr.acinq.bitcoin.Crypto.PrivateKey import fr.acinq.bitcoin.{Block, ByteVector32, Script} import fr.acinq.eclair.NodeParams.BITCOIND +import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeratesPerKw} import fr.acinq.eclair.crypto.LocalKeyManager import fr.acinq.eclair.db._ import fr.acinq.eclair.db.sqlite._ @@ -38,6 +38,17 @@ object TestConstants { val pushMsat = 200000000L val feeratePerKw = 10000L + class TestFeeEstimator extends FeeEstimator { + private var currentFeerates = FeeratesPerKw.single(feeratePerKw) + + override def getFeeratePerKb(target: Int): Long = feerateKw2KB(currentFeerates.feePerBlock(target)) + override def getFeeratePerKw(target: Int): Long = currentFeerates.feePerBlock(target) + + def setFeerate(feeratesPerKw: FeeratesPerKw): Unit = { + currentFeerates = feeratesPerKw + } + } + def sqliteInMemory() = DriverManager.getConnection("jdbc:sqlite::memory:") def inMemoryDb(connection: Connection = sqliteInMemory()): Databases = Databases.databaseByConnections(connection, connection, connection) @@ -65,6 +76,7 @@ object TestConstants { toRemoteDelayBlocks = 144, maxToLocalDelayBlocks = 1000, smartfeeNBlocks = 3, + feeEstimator = new TestFeeEstimator, feeBaseMsat = 546000, feeProportionalMillionth = 10, reserveToFundingRatio = 0.01, // note: not used (overridden below) @@ -131,6 +143,7 @@ object TestConstants { toRemoteDelayBlocks = 144, maxToLocalDelayBlocks = 1000, smartfeeNBlocks = 3, + feeEstimator = new TestFeeEstimator, feeBaseMsat = 546000, feeProportionalMillionth = 10, reserveToFundingRatio = 0.01, // note: not used (overridden below) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestkitBaseClass.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestkitBaseClass.scala index 6afa9e45f2..0aaa6fd811 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestkitBaseClass.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestkitBaseClass.scala @@ -32,7 +32,6 @@ abstract class TestkitBaseClass extends TestKit(ActorSystem("test")) with fixtur override def beforeAll { Globals.blockCount.set(400000) - Globals.feeratesPerKw.set(FeeratesPerKw.single(TestConstants.feeratePerKw)) } override def afterEach() { @@ -45,7 +44,6 @@ abstract class TestkitBaseClass extends TestKit(ActorSystem("test")) with fixtur override def afterAll { TestKit.shutdownActorSystem(system) - Globals.feeratesPerKw.set(FeeratesPerKw.single(1)) } } \ No newline at end of file diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala index 8ced14a635..6b2d67fc07 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala @@ -21,9 +21,9 @@ import java.util.UUID import akka.actor.Actor import akka.testkit.{TestFSMRef, TestKitBase, TestProbe} import fr.acinq.bitcoin.{ByteVector32, Crypto} -import fr.acinq.eclair.TestConstants.{Alice, Bob} +import fr.acinq.eclair.TestConstants.{Alice, Bob, TestFeeEstimator} import fr.acinq.eclair.blockchain._ -import fr.acinq.eclair.blockchain.fee.FeeratesPerKw +import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeratesPerKw} import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.Sphinx import fr.acinq.eclair.payment.PaymentLifecycle @@ -48,10 +48,11 @@ trait StateTestsHelperMethods extends TestKitBase { router: TestProbe, relayerA: TestProbe, relayerB: TestProbe, - channelUpdateListener: TestProbe) + channelUpdateListener: TestProbe, + feeEstimator: TestFeeEstimator) def init(nodeParamsA: NodeParams = TestConstants.Alice.nodeParams, nodeParamsB: NodeParams = TestConstants.Bob.nodeParams, wallet: EclairWallet = new TestWallet): SetupFixture = { - Globals.feeratesPerKw.set(FeeratesPerKw.single(TestConstants.feeratePerKw)) + val testFeeEstimator = new TestFeeEstimator val alice2bob = TestProbe() val bob2alice = TestProbe() val alice2blockchain = TestProbe() @@ -62,9 +63,9 @@ trait StateTestsHelperMethods extends TestKitBase { system.eventStream.subscribe(channelUpdateListener.ref, classOf[LocalChannelUpdate]) system.eventStream.subscribe(channelUpdateListener.ref, classOf[LocalChannelDown]) val router = TestProbe() - val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(nodeParamsA, wallet, Bob.nodeParams.nodeId, alice2blockchain.ref, router.ref, relayerA.ref)) - val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(nodeParamsB, wallet, Alice.nodeParams.nodeId, bob2blockchain.ref, router.ref, relayerB.ref)) - SetupFixture(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, router, relayerA, relayerB, channelUpdateListener) + val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(nodeParamsA.copy(feeEstimator = testFeeEstimator), wallet, Bob.nodeParams.nodeId, alice2blockchain.ref, router.ref, relayerA.ref)) + val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(nodeParamsB.copy(feeEstimator = testFeeEstimator), wallet, Alice.nodeParams.nodeId, bob2blockchain.ref, router.ref, relayerB.ref)) + SetupFixture(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, router, relayerA, relayerB, channelUpdateListener, testFeeEstimator) } def reachNormal(setup: SetupFixture, @@ -75,8 +76,6 @@ trait StateTestsHelperMethods extends TestKitBase { val (aliceParams, bobParams) = (Alice.channelParams, Bob.channelParams) val aliceInit = Init(aliceParams.globalFeatures, aliceParams.localFeatures) val bobInit = Init(bobParams.globalFeatures, bobParams.localFeatures) - // reset global feerates (they may have been changed by previous tests) - Globals.feeratesPerKw.set(FeeratesPerKw.single(TestConstants.feeratePerKw)) alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, aliceParams, alice2bob.ref, bobInit, channelFlags) bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, aliceInit) alice2bob.expectMsgType[OpenChannel] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index b37eb9418b..3a1e652efa 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -1388,8 +1388,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx val sender = TestProbe() val fee = UpdateFee(ByteVector32.Zeroes, 100000000) - // we first update the global variable so that we don't trigger a 'fee too different' error - Globals.feeratesPerKw.set(FeeratesPerKw.single(fee.feeratePerKw)) + // we first update the feerates so that we don't trigger a 'fee too different' error + feeEstimator.setFeerate(FeeratesPerKw.single(fee.feeratePerKw)) sender.send(bob, fee) val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray) === CannotAffordFees(channelId(bob), missingSatoshis = 71620000L, reserveSatoshis = 20000L, feesSatoshis = 72400000L).getMessage) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala index dd73f294f4..00e93669a7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala @@ -20,8 +20,9 @@ import java.util.UUID import akka.actor.Status.Failure import akka.testkit.TestProbe -import fr.acinq.bitcoin.Crypto.{PrivateKey} +import fr.acinq.bitcoin.Crypto.PrivateKey import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, Satoshi, ScriptFlags, Transaction} +import fr.acinq.eclair.TestConstants.{Alice, Bob, TestFeeEstimator} import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.blockchain.fee.FeeratesPerKw import fr.acinq.eclair.channel._ @@ -30,7 +31,7 @@ import fr.acinq.eclair.payment._ import fr.acinq.eclair.router.Hop import fr.acinq.eclair.wire.{CommitSig, Error, FailureMessageCodecs, PermanentChannelFailure, RevokeAndAck, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFee, UpdateFulfillHtlc} import fr.acinq.eclair.{Globals, TestConstants, TestkitBaseClass, randomBytes32} -import org.scalatest.Outcome +import org.scalatest.{Outcome, Tag} import scodec.bits.ByteVector import scala.concurrent.duration._ @@ -580,8 +581,8 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx val sender = TestProbe() val fee = UpdateFee(ByteVector32.Zeroes, 100000000) - // we first update the global variable so that we don't trigger a 'fee too different' error - Globals.feeratesPerKw.set(FeeratesPerKw.single(fee.feeratePerKw)) + // we first update the feerates so that we don't trigger a 'fee too different' error + feeEstimator.setFeerate(FeeratesPerKw.single(fee.feeratePerKw)) sender.send(bob, fee) val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray) === CannotAffordFees(channelId(bob), missingSatoshis = 72120000L, reserveSatoshis = 20000L, feesSatoshis = 72400000L).getMessage) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala index 6ae3d67399..e536cb07e9 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala @@ -22,15 +22,15 @@ import akka.actor.Status.Failure import akka.event.LoggingAdapter import akka.testkit.TestProbe import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Satoshi} -import fr.acinq.eclair.TestConstants.Bob +import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair.blockchain._ -import fr.acinq.eclair.blockchain.fee.FeeratesPerKw +import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeratesPerKw} import fr.acinq.eclair.channel.Helpers.Closing import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.states.StateTestsHelperMethods import fr.acinq.eclair.payment.Local import fr.acinq.eclair.wire.{ClosingSigned, Error, Shutdown} -import fr.acinq.eclair.{Globals, TestkitBaseClass} +import fr.acinq.eclair.{Globals, TestConstants, TestkitBaseClass} import org.scalatest.{Outcome, Tag} import scodec.bits.ByteVector @@ -52,7 +52,7 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods reachNormal(setup) val sender = TestProbe() // alice initiates a closing - if (test.tags.contains("fee2")) Globals.feeratesPerKw.set(FeeratesPerKw.single(4319)) else Globals.feeratesPerKw.set(FeeratesPerKw.single(10000)) + if (test.tags.contains("fee2")) feeEstimator.setFeerate(FeeratesPerKw.single(4319)) else feeEstimator.setFeerate(FeeratesPerKw.single(10000)) sender.send(bob, CMD_CLOSE(None)) bob2alice.expectMsgType[Shutdown] bob2alice.forward(alice) @@ -61,7 +61,7 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods // NB: at this point, alice has already computed and sent the first ClosingSigned message // In order to force a fee negotiation, we will change the current fee before forwarding // the Shutdown message to alice, so that alice computes a different initial closing fee. - if (test.tags.contains("fee2")) Globals.feeratesPerKw.set(FeeratesPerKw.single(4316)) else Globals.feeratesPerKw.set(FeeratesPerKw.single(5000)) + if (test.tags.contains("fee2")) feeEstimator.setFeerate(FeeratesPerKw.single(4316)) else feeEstimator.setFeerate(FeeratesPerKw.single(5000)) alice2bob.forward(bob) awaitCond(bob.stateName == NEGOTIATING) withFixture(test.toNoArgTest(setup)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala index ec19d818e7..f6d6d98457 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala @@ -22,9 +22,9 @@ import akka.actor.Status import akka.actor.Status.Failure import akka.testkit.{TestFSMRef, TestProbe} import fr.acinq.bitcoin.{ByteVector32, OutPoint, ScriptFlags, Transaction, TxIn} -import fr.acinq.eclair.TestConstants.{Alice, Bob} +import fr.acinq.eclair.TestConstants.{Alice, Bob, TestFeeEstimator} import fr.acinq.eclair.blockchain._ -import fr.acinq.eclair.blockchain.fee.FeeratesPerKw +import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeratesPerKw} import fr.acinq.eclair.channel.states.StateTestsHelperMethods import fr.acinq.eclair.channel.{Data, State, _} import fr.acinq.eclair.payment._ @@ -43,7 +43,7 @@ import scala.concurrent.duration._ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { - case class FixtureParam(alice: TestFSMRef[State, Data, Channel], bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe, bob2blockchain: TestProbe, relayerA: TestProbe, relayerB: TestProbe, channelUpdateListener: TestProbe, bobCommitTxes: List[PublishableTxs]) + case class FixtureParam(alice: TestFSMRef[State, Data, Channel], bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe, bob2blockchain: TestProbe, relayerA: TestProbe, relayerB: TestProbe, channelUpdateListener: TestProbe, bobCommitTxes: List[PublishableTxs], feeEstimator: TestFeeEstimator) override def withFixture(test: OneArgTest): Outcome = { val setup = init() @@ -80,7 +80,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { bob2blockchain.expectMsgType[WatchConfirmed] awaitCond(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED) awaitCond(bob.stateName == WAIT_FOR_FUNDING_CONFIRMED) - withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayerA, relayerB, channelUpdateListener, Nil))) + withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayerA, relayerB, channelUpdateListener, Nil, feeEstimator))) } } else { within(30 seconds) { @@ -102,7 +102,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { awaitCond(alice.stateName == NORMAL) awaitCond(bob.stateName == NORMAL) - withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayerA, relayerB, channelUpdateListener, bobCommitTxes))) + withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayerA, relayerB, channelUpdateListener, bobCommitTxes, feeEstimator))) } } } @@ -308,7 +308,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { bob2alice.forward(alice) // agreeing on a closing fee val aliceCloseFee = alice2bob.expectMsgType[ClosingSigned].feeSatoshis - Globals.feeratesPerKw.set(FeeratesPerKw.single(100)) + feeEstimator.setFeerate(FeeratesPerKw.single(100)) alice2bob.forward(bob) val bobCloseFee = bob2alice.expectMsgType[ClosingSigned].feeSatoshis bob2alice.forward(alice) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/RustyTestsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/RustyTestsSpec.scala index 2e15d4ce78..55b6f89b5a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/RustyTestsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/RustyTestsSpec.scala @@ -22,7 +22,7 @@ import java.util.concurrent.{CountDownLatch, TimeUnit} import akka.actor.{ActorRef, ActorSystem, Props} import akka.testkit.{TestFSMRef, TestKit, TestProbe} import fr.acinq.bitcoin.ByteVector32 -import fr.acinq.eclair.TestConstants.{Alice, Bob} +import fr.acinq.eclair.TestConstants.{Alice, Bob, TestFeeEstimator} import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.blockchain.fee.FeeratesPerKw import fr.acinq.eclair.channel._ @@ -53,13 +53,14 @@ class RustyTestsSpec extends TestKit(ActorSystem("test")) with Matchers with fix val relayer = paymentHandler val router = TestProbe() val wallet = new TestWallet - val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(Alice.nodeParams, wallet, Bob.nodeParams.nodeId, alice2blockchain.ref, router.ref, relayer)) - val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(Bob.nodeParams, wallet, Alice.nodeParams.nodeId, bob2blockchain.ref, router.ref, relayer)) + val feeEstimator = new TestFeeEstimator + val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(Alice.nodeParams.copy(feeEstimator = feeEstimator), wallet, Bob.nodeParams.nodeId, alice2blockchain.ref, router.ref, relayer)) + val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(Bob.nodeParams.copy(feeEstimator = feeEstimator), wallet, Alice.nodeParams.nodeId, bob2blockchain.ref, router.ref, relayer)) val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures) val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures) // alice and bob will both have 1 000 000 sat - Globals.feeratesPerKw.set(FeeratesPerKw.single(10000)) - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, 2000000, 1000000000, Globals.feeratesPerKw.get.blocks_2, Globals.feeratesPerKw.get.blocks_6, Alice.channelParams, pipe, bobInit, ChannelFlags.Empty) + feeEstimator.setFeerate(FeeratesPerKw.single(10000)) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, 2000000, 1000000000, feeEstimator.getFeeratePerKw(target = 2), feeEstimator.getFeeratePerKw(target = 6), Alice.channelParams, pipe, bobInit, ChannelFlags.Empty) bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, pipe, aliceInit) pipe ! (alice, bob) within(30 seconds) { @@ -84,7 +85,6 @@ class RustyTestsSpec extends TestKit(ActorSystem("test")) with Matchers with fix } override def afterAll { - Globals.feeratesPerKw.set(FeeratesPerKw.single(1)) TestKit.shutdownActorSystem(system) } From 5f4ee9afac3f4b226137790f0c56c6e8bc36f48f Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 5 Jul 2019 12:27:44 +0200 Subject: [PATCH 02/25] Introduce FeeConf object and config block for confirmation targets, remove unused 'smartfeeNBlocks' --- eclair-core/src/main/resources/reference.conf | 9 +++++++++ .../src/main/scala/fr/acinq/eclair/NodeParams.scala | 13 +++++++++---- .../fr/acinq/eclair/transactions/Transactions.scala | 1 + .../test/scala/fr/acinq/eclair/TestConstants.scala | 5 +++-- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf index b2de25ee7c..8d984b4704 100644 --- a/eclair-core/src/main/resources/reference.conf +++ b/eclair-core/src/main/resources/reference.conf @@ -39,6 +39,15 @@ eclair { 72 = 20000 } } + + # number of blocks to target when computing fees for each transaction type + # possible values are: 1, 2, 6, 12, 36, 72 + fee-targets { + funding = 6 + commitment = 2 + mutual-close = 12 + } + min-feerate = 3 // minimum feerate in satoshis per byte smooth-feerate-window = 6 // 1 = no smoothing diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala index d8e8fd2480..60e53d6891 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala @@ -20,7 +20,6 @@ import java.io.File import java.net.InetSocketAddress import java.nio.file.Files import java.util.concurrent.TimeUnit - import com.typesafe.config.{Config, ConfigFactory} import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.{Block, ByteVector32} @@ -31,9 +30,9 @@ import fr.acinq.eclair.crypto.KeyManager import fr.acinq.eclair.db._ import fr.acinq.eclair.router.RouterConf import fr.acinq.eclair.tor.Socks5ProxyParams +import fr.acinq.eclair.transactions.Transactions.FeeConf import fr.acinq.eclair.wire.{Color, NodeAddress} import scodec.bits.ByteVector - import scala.collection.JavaConversions._ import scala.concurrent.duration.FiniteDuration @@ -55,7 +54,6 @@ case class NodeParams(keyManager: KeyManager, toRemoteDelayBlocks: Int, maxToLocalDelayBlocks: Int, minDepthBlocks: Int, - smartfeeNBlocks: Int, feeEstimator: FeeEstimator, feeBaseMsat: Int, feeProportionalMillionth: Int, @@ -66,6 +64,7 @@ case class NodeParams(keyManager: KeyManager, pingInterval: FiniteDuration, pingTimeout: FiniteDuration, pingDisconnect: Boolean, + feeTargets: FeeConf, maxFeerateMismatch: Double, updateFeeMinDiffRatio: Double, autoReconnect: Boolean, @@ -178,6 +177,12 @@ object NodeParams { .toList .map(ip => NodeAddress.fromParts(ip, config.getInt("server.port")).get) ++ torAddress_opt + val feeTargets = FeeConf( + fundingBlockTarget = config.getInt("fee-targets.funding"), + commitmentBlockTarget = config.getInt("fee-targets.commitment"), + mutualCloseBlockTarget = config.getInt("fee-targets.mutual-close") + ) + NodeParams( keyManager = keyManager, alias = nodeAlias, @@ -194,7 +199,6 @@ object NodeParams { toRemoteDelayBlocks = config.getInt("to-remote-delay-blocks"), maxToLocalDelayBlocks = config.getInt("max-to-local-delay-blocks"), minDepthBlocks = config.getInt("mindepth-blocks"), - smartfeeNBlocks = 3, feeEstimator = new FeeEstimator { override def getFeeratePerKb(target: Int): Long = Globals.feeratesPerKB.get().feePerBlock(target) override def getFeeratePerKw(target: Int): Long = Globals.feeratesPerKw.get().feePerBlock(target) @@ -208,6 +212,7 @@ object NodeParams { pingInterval = FiniteDuration(config.getDuration("ping-interval").getSeconds, TimeUnit.SECONDS), pingTimeout = FiniteDuration(config.getDuration("ping-timeout").getSeconds, TimeUnit.SECONDS), pingDisconnect = config.getBoolean("ping-disconnect"), + feeTargets = feeTargets, maxFeerateMismatch = config.getDouble("max-feerate-mismatch"), updateFeeMinDiffRatio = config.getDouble("update-fee_min-diff-ratio"), autoReconnect = config.getBoolean("auto-reconnect"), diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index 0febeaab9e..894b45e979 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -65,6 +65,7 @@ object Transactions { case object OutputNotFound extends RuntimeException(s"output not found (probably trimmed)") with TxGenerationSkipped case object AmountBelowDustLimit extends RuntimeException(s"amount is below dust limit") with TxGenerationSkipped + case class FeeConf(fundingBlockTarget: Int, commitmentBlockTarget: Int, mutualCloseBlockTarget: Int) // @formatter:on /** diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala index ba67d011f6..cf07a067a3 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala @@ -26,6 +26,7 @@ import fr.acinq.eclair.db._ import fr.acinq.eclair.db.sqlite._ import fr.acinq.eclair.io.Peer import fr.acinq.eclair.router.RouterConf +import fr.acinq.eclair.transactions.Transactions.FeeConf import fr.acinq.eclair.wire.{Color, NodeAddress} import scodec.bits.ByteVector import scala.concurrent.duration._ @@ -75,7 +76,6 @@ object TestConstants { minDepthBlocks = 3, toRemoteDelayBlocks = 144, maxToLocalDelayBlocks = 1000, - smartfeeNBlocks = 3, feeEstimator = new TestFeeEstimator, feeBaseMsat = 546000, feeProportionalMillionth = 10, @@ -86,6 +86,7 @@ object TestConstants { pingInterval = 30 seconds, pingTimeout = 10 seconds, pingDisconnect = true, + feeTargets = FeeConf(3, 3, 3), maxFeerateMismatch = 1.5, updateFeeMinDiffRatio = 0.1, autoReconnect = false, @@ -142,7 +143,6 @@ object TestConstants { minDepthBlocks = 3, toRemoteDelayBlocks = 144, maxToLocalDelayBlocks = 1000, - smartfeeNBlocks = 3, feeEstimator = new TestFeeEstimator, feeBaseMsat = 546000, feeProportionalMillionth = 10, @@ -153,6 +153,7 @@ object TestConstants { pingInterval = 30 seconds, pingTimeout = 10 seconds, pingDisconnect = true, + feeTargets = FeeConf(3, 3, 3), maxFeerateMismatch = 1.0, updateFeeMinDiffRatio = 0.1, autoReconnect = false, From 527499b62b2bd1d23bca159b880cc3a2a1c611dc Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 8 Jul 2019 10:22:40 +0200 Subject: [PATCH 03/25] Use a custom confirmation target for commitment transaction --- eclair-core/src/main/resources/reference.conf | 9 ++++----- .../scala/fr/acinq/eclair/channel/Channel.scala | 10 +++++----- .../fr/acinq/eclair/channel/Commitments.scala | 4 ++-- .../scala/fr/acinq/eclair/channel/Helpers.scala | 2 +- .../main/scala/fr/acinq/eclair/io/Peer.scala | 2 +- .../scala/fr/acinq/eclair/TestConstants.scala | 4 ++-- .../channel/states/e/NormalStateSpec.scala | 17 ++++++++++++----- .../channel/states/f/ShutdownStateSpec.scala | 4 ++-- 8 files changed, 29 insertions(+), 23 deletions(-) diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf index 8d984b4704..75a2935faa 100644 --- a/eclair-core/src/main/resources/reference.conf +++ b/eclair-core/src/main/resources/reference.conf @@ -40,12 +40,11 @@ eclair { } } - # number of blocks to target when computing fees for each transaction type - # possible values are: 1, 2, 6, 12, 36, 72 + // number of blocks to target when computing fees for each transaction type, possible values are: 1, 2, 6, 12, 36, 72 fee-targets { - funding = 6 - commitment = 2 - mutual-close = 12 + funding = 6 // confirmation block target for the funding transaction + commitment = 2 // confirmation block target for the commitment transaction (used in force-close scenario) + mutual-close = 12 // confirmation block target for the mutual close transaction } min-feerate = 3 // minimum feerate in satoshis per byte diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 1afb807743..aea6c34a39 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -681,7 +681,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId } case Event(fee: UpdateFee, d: DATA_NORMAL) => - Try(Commitments.receiveFee(d.commitments, fee, nodeParams.maxFeerateMismatch, nodeParams.feeEstimator)) match { + Try(Commitments.receiveFee(nodeParams.feeTargets, d.commitments, fee, nodeParams.maxFeerateMismatch, nodeParams.feeEstimator)) match { case Success(commitments1) => stay using d.copy(commitments = commitments1) case Failure(cause) => handleLocalError(cause, d, Some(fee)) } @@ -853,7 +853,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId handleLocalError(HtlcTimedout(d.channelId, d.commitments.timedoutOutgoingHtlcs(count)), d, Some(c)) case Event(c@CurrentFeerates(feeratesPerKw), d: DATA_NORMAL) => - val networkFeeratePerKw = feeratesPerKw.feePerBlock(target = 2) + val networkFeeratePerKw = feeratesPerKw.feePerBlock(target = nodeParams.feeTargets.commitmentBlockTarget) d.commitments.localParams.isFunder match { case true if Helpers.shouldUpdateFee(d.commitments.localCommit.spec.feeratePerKw, networkFeeratePerKw, nodeParams.updateFeeMinDiffRatio) => self ! CMD_UPDATE_FEE(networkFeeratePerKw, commit = true) @@ -1040,7 +1040,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId } case Event(fee: UpdateFee, d: DATA_SHUTDOWN) => - Try(Commitments.receiveFee(d.commitments, fee, nodeParams.maxFeerateMismatch, nodeParams.feeEstimator)) match { + Try(Commitments.receiveFee(nodeParams.feeTargets, d.commitments, fee, nodeParams.maxFeerateMismatch, nodeParams.feeEstimator)) match { case Success(commitments1) => stay using d.copy(commitments = commitments1) case Failure(cause) => handleLocalError(cause, d, Some(fee)) } @@ -1135,8 +1135,8 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId case Event(c@CurrentBlockCount(count), d: DATA_SHUTDOWN) if d.commitments.timedoutOutgoingHtlcs(count).nonEmpty => handleLocalError(HtlcTimedout(d.channelId, d.commitments.timedoutOutgoingHtlcs(count)), d, Some(c)) - case Event(c@CurrentFeerates(feerates), d: DATA_SHUTDOWN) => - val networkFeeratePerKw = feerates.feePerBlock(target = 2) + case Event(c@CurrentFeerates(feeratesPerKw), d: DATA_SHUTDOWN) => + val networkFeeratePerKw = feeratesPerKw.feePerBlock(target = nodeParams.feeTargets.commitmentBlockTarget) d.commitments.localParams.isFunder match { case true if Helpers.shouldUpdateFee(d.commitments.localCommit.spec.feeratePerKw, networkFeeratePerKw, nodeParams.updateFeeMinDiffRatio) => self ! CMD_UPDATE_FEE(networkFeeratePerKw, commit = true) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index cd329f8747..06cfc54729 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -311,7 +311,7 @@ object Commitments { (commitments1, fee) } - def receiveFee(commitments: Commitments, fee: UpdateFee, maxFeerateMismatch: Double, feeEstimator: FeeEstimator): Commitments = { + def receiveFee(feeTargets: FeeConf, commitments: Commitments, fee: UpdateFee, maxFeerateMismatch: Double, feeEstimator: FeeEstimator): Commitments = { if (commitments.localParams.isFunder) { throw FundeeCannotSendUpdateFee(commitments.channelId) } @@ -320,7 +320,7 @@ object Commitments { throw FeerateTooSmall(commitments.channelId, remoteFeeratePerKw = fee.feeratePerKw) } - val localFeeratePerKw = feeEstimator.getFeeratePerKw(target = 2) + val localFeeratePerKw = feeEstimator.getFeeratePerKw(target = feeTargets.commitmentBlockTarget) if (Helpers.isFeeDiffTooHigh(fee.feeratePerKw, localFeeratePerKw, maxFeerateMismatch)) { throw FeerateTooDifferent(commitments.channelId, localFeeratePerKw = localFeeratePerKw, remoteFeeratePerKw = fee.feeratePerKw) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 13b51a1bdf..abb018549e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -112,7 +112,7 @@ object Helpers { throw ChannelReserveNotMet(open.temporaryChannelId, toLocalMsat, toRemoteMsat, open.channelReserveSatoshis) } - val localFeeratePerKw = nodeParams.feeEstimator.getFeeratePerKw(target = 2) + val localFeeratePerKw = nodeParams.feeEstimator.getFeeratePerKw(target = nodeParams.feeTargets.commitmentBlockTarget) if (isFeeDiffTooHigh(open.feeratePerKw, localFeeratePerKw, nodeParams.maxFeerateMismatch)) throw FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.feeratePerKw) // only enforce dust limit check on mainnet if (nodeParams.chainHash == Block.LivenetGenesisBlock.hash) { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index eb4f8b2bf6..292a932f7b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -283,7 +283,7 @@ class Peer(val nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: A val (channel, localParams) = createNewChannel(nodeParams, funder = true, c.fundingSatoshis.toLong, origin_opt = Some(sender)) c.timeout_opt.map(openTimeout => context.system.scheduler.scheduleOnce(openTimeout.duration, channel, Channel.TickChannelOpenTimeout)(context.dispatcher)) val temporaryChannelId = randomBytes32 - val channelFeeratePerKw = nodeParams.feeEstimator.getFeeratePerKw(target = 2) + val channelFeeratePerKw = nodeParams.feeEstimator.getFeeratePerKw(target = nodeParams.feeTargets.commitmentBlockTarget) val fundingTxFeeratePerKw = c.fundingTxFeeratePerKw_opt.getOrElse(nodeParams.feeEstimator.getFeeratePerKw(target = 6)) log.info(s"requesting a new channel with fundingSatoshis=${c.fundingSatoshis}, pushMsat=${c.pushMsat} and fundingFeeratePerByte=${c.fundingTxFeeratePerKw_opt} temporaryChannelId=$temporaryChannelId localParams=$localParams") channel ! INPUT_INIT_FUNDER(temporaryChannelId, c.fundingSatoshis.amount, c.pushMsat.amount, channelFeeratePerKw, fundingTxFeeratePerKw, localParams, d.transport, d.remoteInit, c.channelFlags.getOrElse(nodeParams.channelFlags)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala index cf07a067a3..71358814c5 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala @@ -86,7 +86,7 @@ object TestConstants { pingInterval = 30 seconds, pingTimeout = 10 seconds, pingDisconnect = true, - feeTargets = FeeConf(3, 3, 3), + feeTargets = FeeConf(6, 2, 2), maxFeerateMismatch = 1.5, updateFeeMinDiffRatio = 0.1, autoReconnect = false, @@ -153,7 +153,7 @@ object TestConstants { pingInterval = 30 seconds, pingTimeout = 10 seconds, pingDisconnect = true, - feeTargets = FeeConf(3, 3, 3), + feeTargets = FeeConf(6, 2, 2), maxFeerateMismatch = 1.0, updateFeeMinDiffRatio = 0.1, autoReconnect = false, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 3a1e652efa..41452b239b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -680,7 +680,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.expectMsg("ok") // actual test begins (note that channel sends a CMD_SIGN to itself when it receives RevokeAndAck and there are changes) - alice2bob.expectMsgType[UpdateFee] + val updateFee = alice2bob.expectMsgType[UpdateFee] + assert(updateFee.feeratePerKw == TestConstants.feeratePerKw + 1000) alice2bob.forward(bob) alice2bob.expectMsgType[CommitSig] alice2bob.forward(bob) @@ -1403,11 +1404,16 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { test("recv UpdateFee (local/remote feerates are too different)") { f => import f._ + feeEstimator.setFeerate(FeeratesPerKw(1000, 2000, 6000, 12000, 36000, 72000)) val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx val sender = TestProbe() - sender.send(bob, UpdateFee(ByteVector32.Zeroes, 85000)) + // Alice will use $localFeeRate when performing the checks for update_fee + val localFeeRate = feeEstimator.getFeeratePerKw(Alice.nodeParams.feeTargets.commitmentBlockTarget) + assert(localFeeRate === 2000) + val remoteFeeUpdate = 85000 + sender.send(bob, UpdateFee(ByteVector32.Zeroes, remoteFeeUpdate)) val error = bob2alice.expectMsgType[Error] - assert(new String(error.data.toArray) === "local/remote feerates are too different: remoteFeeratePerKw=85000 localFeeratePerKw=10000") + assert(new String(error.data.toArray) === s"local/remote feerates are too different: remoteFeeratePerKw=$remoteFeeUpdate localFeeratePerKw=$localFeeRate") awaitCond(bob.stateName == CLOSING) // channel should be advertised as down assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId) @@ -1420,6 +1426,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { import f._ val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx val sender = TestProbe() + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.feeratePerKw == feeEstimator.getFeeratePerKw(Bob.nodeParams.feeTargets.commitmentBlockTarget)) sender.send(bob, UpdateFee(ByteVector32.Zeroes, 252)) val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray) === "remote fee rate is too small: remoteFeeratePerKw=252") @@ -1685,9 +1692,9 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { import f._ val sender = TestProbe() val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - val event = CurrentFeerates(FeeratesPerKw.single(20000)) + val event = CurrentFeerates(FeeratesPerKw(100, 200, 600, 1200, 3600, 7200)) sender.send(alice, event) - alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, event.feeratesPerKw.blocks_2)) + alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, event.feeratesPerKw.feePerBlock(Alice.nodeParams.feeTargets.commitmentBlockTarget))) } test("recv CurrentFeerate (when funder, doesn't trigger an UpdateFee)") { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala index 00e93669a7..050016816b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala @@ -646,9 +646,9 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { import f._ val sender = TestProbe() val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] - val event = CurrentFeerates(FeeratesPerKw.single(20000)) + val event = CurrentFeerates(FeeratesPerKw(100, 200, 600, 1200, 3600, 7200)) sender.send(alice, event) - alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, event.feeratesPerKw.blocks_2)) + alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, event.feeratesPerKw.feePerBlock(Alice.nodeParams.feeTargets.commitmentBlockTarget))) } test("recv CurrentFeerate (when funder, doesn't trigger an UpdateFee)") { f => From dade6fde8a73ee0b473d5a5fb12988bac0646c69 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 8 Jul 2019 13:00:10 +0200 Subject: [PATCH 04/25] Use a custom confirmation target for funding transaction --- .../fr/acinq/eclair/channel/Channel.scala | 6 ++-- .../acinq/eclair/channel/ChannelEvents.scala | 2 +- .../fr/acinq/eclair/channel/Register.scala | 2 +- .../main/scala/fr/acinq/eclair/io/Peer.scala | 2 +- .../scala/fr/acinq/eclair/io/PeerSpec.scala | 33 +++++++++++++++---- .../fr/acinq/eclair/gui/GUIUpdater.scala | 2 +- 6 files changed, 34 insertions(+), 13 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index aea6c34a39..d9b36a1d1c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -145,8 +145,8 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId startWith(WAIT_FOR_INIT_INTERNAL, Nothing) when(WAIT_FOR_INIT_INTERNAL)(handleExceptions { - case Event(initFunder@INPUT_INIT_FUNDER(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, _, localParams, remote, _, channelFlags), Nothing) => - context.system.eventStream.publish(ChannelCreated(self, context.parent, remoteNodeId, true, temporaryChannelId)) + case Event(initFunder@INPUT_INIT_FUNDER(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTxFeeratePerKw, localParams, remote, _, channelFlags), Nothing) => + context.system.eventStream.publish(ChannelCreated(self, context.parent, remoteNodeId, true, temporaryChannelId, initialFeeratePerKw, Some(fundingTxFeeratePerKw))) forwarder ! remote val open = OpenChannel(nodeParams.chainHash, temporaryChannelId = temporaryChannelId, @@ -270,7 +270,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId Try(Helpers.validateParamsFundee(nodeParams, open)) match { case Failure(t) => handleLocalError(t, d, Some(open)) case Success(_) => - context.system.eventStream.publish(ChannelCreated(self, context.parent, remoteNodeId, false, open.temporaryChannelId)) + context.system.eventStream.publish(ChannelCreated(self, context.parent, remoteNodeId, false, open.temporaryChannelId, open.feeratePerKw, None)) // TODO: maybe also check uniqueness of temporary channel id val minimumDepth = nodeParams.minDepthBlocks val accept = AcceptChannel(temporaryChannelId = open.temporaryChannelId, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala index 6b3c128d22..e9104a7db4 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala @@ -30,7 +30,7 @@ import fr.acinq.eclair.wire.{ChannelAnnouncement, ChannelUpdate} trait ChannelEvent -case class ChannelCreated(channel: ActorRef, peer: ActorRef, remoteNodeId: PublicKey, isFunder: Boolean, temporaryChannelId: ByteVector32) extends ChannelEvent +case class ChannelCreated(channel: ActorRef, peer: ActorRef, remoteNodeId: PublicKey, isFunder: Boolean, temporaryChannelId: ByteVector32, initialFeeratePerKw: Long, fundingTxFeeratePerKw: Option[Long]) extends ChannelEvent case class ChannelRestored(channel: ActorRef, peer: ActorRef, remoteNodeId: PublicKey, isFunder: Boolean, channelId: ByteVector32, currentData: HasCommitments) extends ChannelEvent diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Register.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Register.scala index 8d3794de0a..0f65bcecd6 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Register.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Register.scala @@ -37,7 +37,7 @@ class Register extends Actor with ActorLogging { override def receive: Receive = main(Map.empty, Map.empty, Map.empty) def main(channels: Map[ByteVector32, ActorRef], shortIds: Map[ShortChannelId, ByteVector32], channelsTo: Map[ByteVector32, PublicKey]): Receive = { - case ChannelCreated(channel, _, remoteNodeId, _, temporaryChannelId) => + case ChannelCreated(channel, _, remoteNodeId, _, temporaryChannelId, _, _) => context.watch(channel) context become main(channels + (temporaryChannelId -> channel), shortIds, channelsTo + (temporaryChannelId -> remoteNodeId)) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index 292a932f7b..1c9b573a76 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -284,7 +284,7 @@ class Peer(val nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: A c.timeout_opt.map(openTimeout => context.system.scheduler.scheduleOnce(openTimeout.duration, channel, Channel.TickChannelOpenTimeout)(context.dispatcher)) val temporaryChannelId = randomBytes32 val channelFeeratePerKw = nodeParams.feeEstimator.getFeeratePerKw(target = nodeParams.feeTargets.commitmentBlockTarget) - val fundingTxFeeratePerKw = c.fundingTxFeeratePerKw_opt.getOrElse(nodeParams.feeEstimator.getFeeratePerKw(target = 6)) + val fundingTxFeeratePerKw = c.fundingTxFeeratePerKw_opt.getOrElse(nodeParams.feeEstimator.getFeeratePerKw(target = nodeParams.feeTargets.fundingBlockTarget)) log.info(s"requesting a new channel with fundingSatoshis=${c.fundingSatoshis}, pushMsat=${c.pushMsat} and fundingFeeratePerByte=${c.fundingTxFeeratePerKw_opt} temporaryChannelId=$temporaryChannelId localParams=$localParams") channel ! INPUT_INIT_FUNDER(temporaryChannelId, c.fundingSatoshis.amount, c.pushMsat.amount, channelFeeratePerKw, fundingTxFeeratePerKw, localParams, d.transport, d.remoteInit, c.channelFlags.getOrElse(nodeParams.channelFlags)) stay using d.copy(channels = d.channels + (TemporaryChannelId(temporaryChannelId) -> channel)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala index 82617ea236..1046cd8624 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala @@ -16,16 +16,20 @@ package fr.acinq.eclair.io +import java.net.{Inet4Address, InetSocketAddress} + +import akka.actor.{ActorRef, ActorSystem, FSM, PoisonPill, Terminated} import java.net.{Inet4Address, InetAddress, InetSocketAddress, ServerSocket} import akka.actor.FSM.{CurrentState, SubscribeTransitionCallBack, Transition} import akka.actor.{ActorRef, PoisonPill} import akka.testkit.{TestFSMRef, TestProbe} import fr.acinq.bitcoin.Crypto.PublicKey +import fr.acinq.bitcoin.{MilliSatoshi, Satoshi} import fr.acinq.eclair.TestConstants._ import fr.acinq.eclair._ -import fr.acinq.eclair.blockchain.EclairWallet -import fr.acinq.eclair.channel.HasCommitments +import fr.acinq.eclair.blockchain.{EclairWallet, TestWallet} +import fr.acinq.eclair.channel.{ChannelCreated, Data, HasCommitments, State} import fr.acinq.eclair.crypto.TransportHandler import fr.acinq.eclair.io.Peer._ import fr.acinq.eclair.router.RoutingSyncSpec.makeFakeRoutingInfo @@ -47,10 +51,11 @@ class PeerSpec extends TestkitBaseClass { val updates = (fakeRoutingInfo.map(_._2) ++ fakeRoutingInfo.map(_._3)).toList val nodes = (fakeRoutingInfo.map(_._4) ++ fakeRoutingInfo.map(_._5)).toList - case class FixtureParam(remoteNodeId: PublicKey, authenticator: TestProbe, watcher: TestProbe, router: TestProbe, relayer: TestProbe, connection: TestProbe, transport: TestProbe, peer: TestFSMRef[Peer.State, Peer.Data, Peer]) + case class FixtureParam(remoteNodeId: PublicKey, authenticator: TestProbe, watcher: TestProbe, router: TestProbe, relayer: TestProbe, connection: TestProbe, transport: TestProbe, peer: TestFSMRef[Peer.State, Peer.Data, Peer], feeEstimator: TestFeeEstimator) override protected def withFixture(test: OneArgTest): Outcome = { - val aParams = Alice.nodeParams + val feeEstimator = new TestFeeEstimator + val aParams = Alice.nodeParams.copy(feeEstimator = feeEstimator) val aliceParams = test.tags.contains("with_node_announcements") match { case true => val bobAnnouncement = NodeAnnouncement(randomBytes64, ByteVector.empty, 1, Bob.nodeParams.nodeId, Color(100.toByte, 200.toByte, 300.toByte), "node-alias", fakeIPAddress :: Nil) @@ -65,10 +70,10 @@ class PeerSpec extends TestkitBaseClass { val relayer = TestProbe() val connection = TestProbe() val transport = TestProbe() - val wallet: EclairWallet = null // unused + val wallet: EclairWallet = new TestWallet val remoteNodeId = Bob.nodeParams.nodeId val peer: TestFSMRef[Peer.State, Peer.Data, Peer] = TestFSMRef(new Peer(aliceParams, remoteNodeId, authenticator.ref, watcher.ref, router.ref, relayer.ref, wallet)) - withFixture(test.toNoArgTest(FixtureParam(remoteNodeId, authenticator, watcher, router, relayer, connection, transport, peer))) + withFixture(test.toNoArgTest(FixtureParam(remoteNodeId, authenticator, watcher, router, relayer, connection, transport, peer, feeEstimator))) } def connect(remoteNodeId: PublicKey, authenticator: TestProbe, watcher: TestProbe, router: TestProbe, relayer: TestProbe, connection: TestProbe, transport: TestProbe, peer: ActorRef, channels: Set[HasCommitments] = Set.empty): Unit = { @@ -237,6 +242,22 @@ class PeerSpec extends TestkitBaseClass { probe.expectMsg("disconnecting") } + test("use correct fee rates when spawning a channel") { f => + import f._ + + val probe = TestProbe() + system.eventStream.subscribe(probe.ref, classOf[ChannelCreated]) + connect(remoteNodeId, authenticator, watcher, router, relayer, connection, transport, peer) + + assert(peer.stateData.channels.isEmpty) + probe.send(peer, Peer.OpenChannel(remoteNodeId, Satoshi(12300), MilliSatoshi(0), None, None, None)) + awaitCond(peer.stateData.channels.nonEmpty) + + val channelCreated = probe.expectMsgType[ChannelCreated] + assert(channelCreated.initialFeeratePerKw == feeEstimator.getFeeratePerKw(Alice.nodeParams.feeTargets.commitmentBlockTarget)) + assert(channelCreated.fundingTxFeeratePerKw.get == feeEstimator.getFeeratePerKw(Alice.nodeParams.feeTargets.fundingBlockTarget)) + } + test("reply to ping") { f => import f._ val probe = TestProbe() diff --git a/eclair-node-gui/src/main/scala/fr/acinq/eclair/gui/GUIUpdater.scala b/eclair-node-gui/src/main/scala/fr/acinq/eclair/gui/GUIUpdater.scala index d63969c9b0..ed96e755f0 100644 --- a/eclair-node-gui/src/main/scala/fr/acinq/eclair/gui/GUIUpdater.scala +++ b/eclair-node-gui/src/main/scala/fr/acinq/eclair/gui/GUIUpdater.scala @@ -75,7 +75,7 @@ class GUIUpdater(mainController: MainController) extends Actor with ActorLogging def main(m: Map[ActorRef, ChannelPaneController]): Receive = { - case ChannelCreated(channel, peer, remoteNodeId, isFunder, temporaryChannelId) => + case ChannelCreated(channel, peer, remoteNodeId, isFunder, temporaryChannelId, _, _) => context.watch(channel) val (channelPaneController, root) = createChannelPanel(channel, peer, remoteNodeId, isFunder, temporaryChannelId) runInGuiThread(() => mainController.channelBox.getChildren.addAll(root)) From 3e4ad7a9d51bed6f7e549bfd5f02ebee691fefc8 Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 19 Jul 2019 19:31:19 +0200 Subject: [PATCH 05/25] Use custom confirmation target for mutual close transaction --- .../fr/acinq/eclair/channel/Channel.scala | 10 ++++---- .../fr/acinq/eclair/channel/Helpers.scala | 15 ++++++++---- .../channel/states/h/ClosingStateSpec.scala | 23 +++++++++++++++++++ 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index d9b36a1d1c..a8f71dfbb4 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -836,7 +836,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId // there are no pending signed htlcs, let's go directly to NEGOTIATING if (d.commitments.localParams.isFunder) { // we are funder, need to initiate the negotiation by sending the first closing_signed - val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, d.commitments, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey, nodeParams.feeEstimator) + val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, d.commitments, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey, nodeParams.feeEstimator, nodeParams.feeTargets) goto(NEGOTIATING) using store(DATA_NEGOTIATING(d.commitments, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending sendList :+ closingSigned } else { // we are fundee, will wait for their closing_signed @@ -1079,7 +1079,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId if (commitments1.hasNoPendingHtlcs) { if (d.commitments.localParams.isFunder) { // we are funder, need to initiate the negotiation by sending the first closing_signed - val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey, nodeParams.feeEstimator) + val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey, nodeParams.feeEstimator, nodeParams.feeTargets) goto(NEGOTIATING) using store(DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending revocation :: closingSigned :: Nil } else { // we are fundee, will wait for their closing_signed @@ -1115,7 +1115,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId log.debug(s"switching to NEGOTIATING spec:\n${Commitments.specs2String(commitments1)}") if (d.commitments.localParams.isFunder) { // we are funder, need to initiate the negotiation by sending the first closing_signed - val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey, nodeParams.feeEstimator) + val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey, nodeParams.feeEstimator, nodeParams.feeTargets) goto(NEGOTIATING) using store(DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending closingSigned } else { // we are fundee, will wait for their closing_signed @@ -1169,7 +1169,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId // if we are fundee and we were waiting for them to send their first closing_signed, we don't have a lastLocalClosingFee, so we compute a firstClosingFee val lastLocalClosingFee = d.closingTxProposed.last.lastOption.map(_.localClosingSigned.feeSatoshis).map(Satoshi) val nextClosingFee = Closing.nextClosingFee( - localClosingFee = lastLocalClosingFee.getOrElse(Closing.firstClosingFee(d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.feeEstimator)), + localClosingFee = lastLocalClosingFee.getOrElse(Closing.firstClosingFee(d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.feeEstimator, nodeParams.feeTargets)), remoteClosingFee = Satoshi(remoteClosingFee)) val (closingTx, closingSigned) = Closing.makeClosingTx(keyManager, d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nextClosingFee) if (Some(nextClosingFee) == lastLocalClosingFee) { @@ -1551,7 +1551,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId // note: in any case we still need to keep all previously sent closing_signed, because they may publish one of them if (d.commitments.localParams.isFunder) { // we could use the last closing_signed we sent, but network fees may have changed while we were offline so it is better to restart from scratch - val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.feeEstimator) + val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.feeEstimator, nodeParams.feeTargets) val closingTxProposed1 = d.closingTxProposed :+ List(ClosingTxProposed(closingTx.tx, closingSigned)) goto(NEGOTIATING) using store(d.copy(closingTxProposed = closingTxProposed1)) sending d.localShutdown :: closingSigned :: Nil } else { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index abb018549e..28b3eb1332 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -424,21 +424,26 @@ object Helpers { } } - def firstClosingFee(commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feeEstimator: FeeEstimator)(implicit log: LoggingAdapter): Satoshi = { + def firstClosingFee(feeratePerKw: Long, commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector)(implicit log: LoggingAdapter): Satoshi = { import commitments._ // this is just to estimate the weight, it depends on size of the pubkey scripts val dummyClosingTx = Transactions.makeClosingTx(commitInput, localScriptPubkey, remoteScriptPubkey, localParams.isFunder, Satoshi(0), Satoshi(0), localCommit.spec) val closingWeight = Transaction.weight(Transactions.addSigs(dummyClosingTx, dummyPublicKey, remoteParams.fundingPubKey, Transactions.PlaceHolderSig, Transactions.PlaceHolderSig).tx) - // no need to use a very high fee here, so we target 6 blocks; also, we "MUST set fee_satoshis less than or equal to the base fee of the final commitment transaction" - val feeratePerKw = Math.min(feeEstimator.getFeeratePerKw(target = 6), commitments.localCommit.spec.feeratePerKw) log.info(s"using feeratePerKw=$feeratePerKw for initial closing tx") Transactions.weight2fee(feeratePerKw, closingWeight) } + def firstClosingFee(commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feeEstimator: FeeEstimator, feeConf: FeeConf)(implicit log: LoggingAdapter): Satoshi = { + // we "MUST set fee_satoshis less than or equal to the base fee of the final commitment transaction" + val blockTarget = Math.max(feeConf.mutualCloseBlockTarget, feeConf.commitmentBlockTarget) + val feeratePerKw = feeEstimator.getFeeratePerKw(blockTarget) + firstClosingFee(feeratePerKw, commitments, localScriptPubkey, remoteScriptPubkey) + } + def nextClosingFee(localClosingFee: Satoshi, remoteClosingFee: Satoshi): Satoshi = ((localClosingFee + remoteClosingFee) / 4) * 2 - def makeFirstClosingTx(keyManager: KeyManager, commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feeEstimator: FeeEstimator)(implicit log: LoggingAdapter): (ClosingTx, ClosingSigned) = { - val closingFee = firstClosingFee(commitments, localScriptPubkey, remoteScriptPubkey, feeEstimator) + def makeFirstClosingTx(keyManager: KeyManager, commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feeEstimator: FeeEstimator, feeConf: FeeConf)(implicit log: LoggingAdapter): (ClosingTx, ClosingSigned) = { + val closingFee = firstClosingFee(commitments, localScriptPubkey, remoteScriptPubkey, feeEstimator, feeConf) makeClosingTx(keyManager, commitments, localScriptPubkey, remoteScriptPubkey, closingFee) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala index f6d6d98457..e6511997e5 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala @@ -21,9 +21,12 @@ import java.util.UUID import akka.actor.Status import akka.actor.Status.Failure import akka.testkit.{TestFSMRef, TestProbe} +import com.typesafe.sslconfig.util.NoopLogger import fr.acinq.bitcoin.{ByteVector32, OutPoint, ScriptFlags, Transaction, TxIn} import fr.acinq.eclair.TestConstants.{Alice, Bob, TestFeeEstimator} import fr.acinq.eclair.blockchain._ +import fr.acinq.eclair.blockchain.fee.FeeratesPerKw +import fr.acinq.eclair.channel.Helpers.Closing import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeratesPerKw} import fr.acinq.eclair.channel.states.StateTestsHelperMethods import fr.acinq.eclair.channel.{Data, State, _} @@ -137,6 +140,26 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { // both nodes are now in CLOSING state with a mutual close tx pending for confirmation } + test("start fee negotiation from configured block target") { f => + import f._ + + feeEstimator.setFeerate(FeeratesPerKw(100, 250, 350, 450, 600, 800)) + + val sender = TestProbe() + // alice initiates a closing + sender.send(alice, CMD_CLOSE(None)) + alice2bob.expectMsgType[Shutdown] + alice2bob.forward(bob) + bob2alice.expectMsgType[Shutdown] + bob2alice.forward(alice) + val closing = alice2bob.expectMsgType[ClosingSigned] + val aliceData = alice.stateData.asInstanceOf[DATA_NEGOTIATING] + val mutualClosingFeeRate = feeEstimator.getFeeratePerKw(Alice.nodeParams.feeTargets.mutualCloseBlockTarget) + val expectedFirstProposedFee = Closing.firstClosingFee(mutualClosingFeeRate, aliceData.commitments, aliceData.localShutdown.scriptPubKey, aliceData.remoteShutdown.scriptPubKey)(akka.event.NoLogging) + assert(Alice.nodeParams.feeTargets.mutualCloseBlockTarget == 2 && mutualClosingFeeRate == 250) + assert(closing.feeSatoshis == expectedFirstProposedFee.amount) + } + test("recv BITCOIN_FUNDING_PUBLISH_FAILED", Tag("funding_unconfirmed")) { f => import f._ alice ! CMD_FORCECLOSE From 214494b1f57460e9623c69871f71b9f02a9e76b6 Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 19 Jul 2019 19:40:23 +0200 Subject: [PATCH 06/25] Use custom confirmation target for claim transactions --- eclair-core/src/main/resources/reference.conf | 7 ++--- .../scala/fr/acinq/eclair/NodeParams.scala | 9 ++++--- .../fr/acinq/eclair/channel/Channel.scala | 18 ++++++------- .../fr/acinq/eclair/channel/Commitments.scala | 2 +- .../fr/acinq/eclair/channel/Helpers.scala | 27 +++++++++---------- .../eclair/transactions/Transactions.scala | 2 +- .../scala/fr/acinq/eclair/TestConstants.scala | 6 ++--- .../channel/states/e/NormalStateSpec.scala | 11 ++++++-- 8 files changed, 44 insertions(+), 38 deletions(-) diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf index 75a2935faa..4afbf55cc8 100644 --- a/eclair-core/src/main/resources/reference.conf +++ b/eclair-core/src/main/resources/reference.conf @@ -42,9 +42,10 @@ eclair { // number of blocks to target when computing fees for each transaction type, possible values are: 1, 2, 6, 12, 36, 72 fee-targets { - funding = 6 // confirmation block target for the funding transaction - commitment = 2 // confirmation block target for the commitment transaction (used in force-close scenario) - mutual-close = 12 // confirmation block target for the mutual close transaction + funding = 6 // target for the funding transaction + commitment = 2 // target for the commitment transaction (used in force-close scenario) + mutual-close = 12 // target for the mutual close transaction + claim-main = 6 // target for the claim main transaction (tx that spends main channel output back to wallet) } min-feerate = 3 // minimum feerate in satoshis per byte diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala index 60e53d6891..9536e2cdfd 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala @@ -30,7 +30,7 @@ import fr.acinq.eclair.crypto.KeyManager import fr.acinq.eclair.db._ import fr.acinq.eclair.router.RouterConf import fr.acinq.eclair.tor.Socks5ProxyParams -import fr.acinq.eclair.transactions.Transactions.FeeConf +import fr.acinq.eclair.transactions.Transactions.FeeTargets import fr.acinq.eclair.wire.{Color, NodeAddress} import scodec.bits.ByteVector import scala.collection.JavaConversions._ @@ -64,7 +64,7 @@ case class NodeParams(keyManager: KeyManager, pingInterval: FiniteDuration, pingTimeout: FiniteDuration, pingDisconnect: Boolean, - feeTargets: FeeConf, + feeTargets: FeeTargets, maxFeerateMismatch: Double, updateFeeMinDiffRatio: Double, autoReconnect: Boolean, @@ -177,10 +177,11 @@ object NodeParams { .toList .map(ip => NodeAddress.fromParts(ip, config.getInt("server.port")).get) ++ torAddress_opt - val feeTargets = FeeConf( + val feeTargets = FeeTargets( fundingBlockTarget = config.getInt("fee-targets.funding"), commitmentBlockTarget = config.getInt("fee-targets.commitment"), - mutualCloseBlockTarget = config.getInt("fee-targets.mutual-close") + mutualCloseBlockTarget = config.getInt("fee-targets.mutual-close"), + claimMainBlockTarget = config.getInt("fee-targets.claim-main") ) NodeParams( diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index a8f71dfbb4..55fff55426 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -1214,19 +1214,19 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId log.info(s"got valid payment preimage, recalculating transactions to redeem the corresponding htlc on-chain") val localCommitPublished1 = d.localCommitPublished.map { case localCommitPublished => - val localCommitPublished1 = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, commitments1, localCommitPublished.commitTx, nodeParams.feeEstimator) + val localCommitPublished1 = Helpers.Closing.claimCurrentLocalCommitTxOutputs(nodeParams.feeTargets, keyManager, commitments1, localCommitPublished.commitTx, nodeParams.feeEstimator) doPublish(localCommitPublished1) localCommitPublished1 } val remoteCommitPublished1 = d.remoteCommitPublished.map { case remoteCommitPublished => - val remoteCommitPublished1 = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, commitments1, commitments1.remoteCommit, remoteCommitPublished.commitTx, nodeParams.feeEstimator) + val remoteCommitPublished1 = Helpers.Closing.claimRemoteCommitTxOutputs(nodeParams.feeTargets, keyManager, commitments1, commitments1.remoteCommit, remoteCommitPublished.commitTx, nodeParams.feeEstimator) doPublish(remoteCommitPublished1) remoteCommitPublished1 } val nextRemoteCommitPublished1 = d.nextRemoteCommitPublished.map { case remoteCommitPublished => - val remoteCommitPublished1 = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, commitments1, commitments1.remoteCommit, remoteCommitPublished.commitTx, nodeParams.feeEstimator) + val remoteCommitPublished1 = Helpers.Closing.claimRemoteCommitTxOutputs(nodeParams.feeTargets, keyManager, commitments1, commitments1.remoteCommit, remoteCommitPublished.commitTx, nodeParams.feeEstimator) doPublish(remoteCommitPublished1) remoteCommitPublished1 } @@ -1906,7 +1906,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId } else { val commitTx = d.commitments.localCommit.publishableTxs.commitTx.tx - val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, d.commitments, commitTx, nodeParams.feeEstimator) + val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(nodeParams.feeTargets, keyManager, d.commitments, commitTx, nodeParams.feeEstimator) doPublish(localCommitPublished) val nextData = d match { @@ -1981,7 +1981,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId log.warning(s"they published their current commit in txid=${commitTx.txid}") require(commitTx.txid == d.commitments.remoteCommit.txid, "txid mismatch") - val remoteCommitPublished = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, d.commitments, d.commitments.remoteCommit, commitTx, nodeParams.feeEstimator) + val remoteCommitPublished = Helpers.Closing.claimRemoteCommitTxOutputs(nodeParams.feeTargets, keyManager, d.commitments, d.commitments.remoteCommit, commitTx, nodeParams.feeEstimator) doPublish(remoteCommitPublished) val nextData = d match { @@ -1998,7 +1998,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId log.warning(s"they published their future commit (because we asked them to) in txid=${commitTx.txid}") // if we are in this state, then this field is defined val remotePerCommitmentPoint = d.remoteChannelReestablish.myCurrentPerCommitmentPoint.get - val remoteCommitPublished = Helpers.Closing.claimRemoteCommitMainOutput(keyManager, d.commitments, remotePerCommitmentPoint, commitTx, nodeParams.feeEstimator) + val remoteCommitPublished = Helpers.Closing.claimRemoteCommitMainOutput(nodeParams.feeTargets, keyManager, d.commitments, remotePerCommitmentPoint, commitTx, nodeParams.feeEstimator) val nextData = DATA_CLOSING(d.commitments, fundingTx = None, waitingSince = now, Nil, futureRemoteCommitPublished = Some(remoteCommitPublished)) doPublish(remoteCommitPublished) @@ -2011,7 +2011,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId val remoteCommit = d.commitments.remoteNextCommitInfo.left.get.nextRemoteCommit require(commitTx.txid == remoteCommit.txid, "txid mismatch") - val remoteCommitPublished = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, d.commitments, remoteCommit, commitTx, nodeParams.feeEstimator) + val remoteCommitPublished = Helpers.Closing.claimRemoteCommitTxOutputs(nodeParams.feeTargets, keyManager, d.commitments, remoteCommit, commitTx, nodeParams.feeEstimator) doPublish(remoteCommitPublished) val nextData = d match { @@ -2044,7 +2044,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId def handleRemoteSpentOther(tx: Transaction, d: HasCommitments) = { log.warning(s"funding tx spent in txid=${tx.txid}") - Helpers.Closing.claimRevokedRemoteCommitTxOutputs(keyManager, d.commitments, tx, nodeParams.db.channels, nodeParams.feeEstimator) match { + Helpers.Closing.claimRevokedRemoteCommitTxOutputs(nodeParams.feeTargets, keyManager, d.commitments, tx, nodeParams.db.channels, nodeParams.feeEstimator) match { case Some(revokedCommitPublished) => log.warning(s"txid=${tx.txid} was a revoked commitment, publishing the penalty tx") val exc = FundingTxSpent(d.channelId, tx) @@ -2091,7 +2091,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId // let's try to spend our current local tx val commitTx = d.commitments.localCommit.publishableTxs.commitTx.tx - val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, d.commitments, commitTx, nodeParams.feeEstimator) + val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(nodeParams.feeTargets, keyManager, d.commitments, commitTx, nodeParams.feeEstimator) doPublish(localCommitPublished) goto(ERR_INFORMATION_LEAK) sending error diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 06cfc54729..d15d9fa064 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -311,7 +311,7 @@ object Commitments { (commitments1, fee) } - def receiveFee(feeTargets: FeeConf, commitments: Commitments, fee: UpdateFee, maxFeerateMismatch: Double, feeEstimator: FeeEstimator): Commitments = { + def receiveFee(feeTargets: FeeTargets, commitments: Commitments, fee: UpdateFee, maxFeerateMismatch: Double, feeEstimator: FeeEstimator): Commitments = { if (commitments.localParams.isFunder) { throw FundeeCannotSendUpdateFee(commitments.channelId) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 28b3eb1332..71982f39af 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -433,17 +433,17 @@ object Helpers { Transactions.weight2fee(feeratePerKw, closingWeight) } - def firstClosingFee(commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feeEstimator: FeeEstimator, feeConf: FeeConf)(implicit log: LoggingAdapter): Satoshi = { + def firstClosingFee(commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): Satoshi = { // we "MUST set fee_satoshis less than or equal to the base fee of the final commitment transaction" - val blockTarget = Math.max(feeConf.mutualCloseBlockTarget, feeConf.commitmentBlockTarget) + val blockTarget = Math.max(feeTargets.mutualCloseBlockTarget, feeTargets.commitmentBlockTarget) val feeratePerKw = feeEstimator.getFeeratePerKw(blockTarget) firstClosingFee(feeratePerKw, commitments, localScriptPubkey, remoteScriptPubkey) } def nextClosingFee(localClosingFee: Satoshi, remoteClosingFee: Satoshi): Satoshi = ((localClosingFee + remoteClosingFee) / 4) * 2 - def makeFirstClosingTx(keyManager: KeyManager, commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feeEstimator: FeeEstimator, feeConf: FeeConf)(implicit log: LoggingAdapter): (ClosingTx, ClosingSigned) = { - val closingFee = firstClosingFee(commitments, localScriptPubkey, remoteScriptPubkey, feeEstimator, feeConf) + def makeFirstClosingTx(keyManager: KeyManager, commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): (ClosingTx, ClosingSigned) = { + val closingFee = firstClosingFee(commitments, localScriptPubkey, remoteScriptPubkey, feeEstimator, feeTargets) makeClosingTx(keyManager, commitments, localScriptPubkey, remoteScriptPubkey, closingFee) } @@ -496,7 +496,7 @@ object Helpers { * @param commitments our commitment data, which include payment preimages * @return a list of transactions (one per HTLC that we can claim) */ - def claimCurrentLocalCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, tx: Transaction, feeEstimator: FeeEstimator)(implicit log: LoggingAdapter): LocalCommitPublished = { + def claimCurrentLocalCommitTxOutputs(feeTargets: FeeTargets, keyManager: KeyManager, commitments: Commitments, tx: Transaction, feeEstimator: FeeEstimator)(implicit log: LoggingAdapter): LocalCommitPublished = { import commitments._ require(localCommit.publishableTxs.commitTx.tx.txid == tx.txid, "txid mismatch, provided tx is not the current local commit tx") @@ -504,8 +504,7 @@ object Helpers { val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint) val localDelayedPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) - // no need to use a high fee rate for delayed transactions (we are the only one who can spend them) - val feeratePerKwDelayed = feeEstimator.getFeeratePerKw(target = 6) + val feeratePerKwDelayed = feeEstimator.getFeeratePerKw(feeTargets.claimMainBlockTarget) // first we will claim our main output as soon as the delay is over val mainDelayedTx = generateTx("main-delayed-output")(Try { @@ -568,7 +567,7 @@ object Helpers { * @param tx the remote commitment transaction that has just been published * @return a list of transactions (one per HTLC that we can claim) */ - def claimRemoteCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, remoteCommit: RemoteCommit, tx: Transaction, feeEstimator: FeeEstimator)(implicit log: LoggingAdapter): RemoteCommitPublished = { + def claimRemoteCommitTxOutputs(feeTargets: FeeTargets, keyManager: KeyManager, commitments: Commitments, remoteCommit: RemoteCommit, tx: Transaction, feeEstimator: FeeEstimator)(implicit log: LoggingAdapter): RemoteCommitPublished = { import commitments.{commitInput, localParams, remoteParams} require(remoteCommit.txid == tx.txid, "txid mismatch, provided tx is not the current remote commit tx") val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, remoteCommit.index, localParams, remoteParams, commitInput, remoteCommit.remotePerCommitmentPoint, remoteCommit.spec) @@ -608,7 +607,7 @@ object Helpers { }) }.toSeq.flatten - claimRemoteCommitMainOutput(keyManager, commitments, remoteCommit.remotePerCommitmentPoint, tx, feeEstimator).copy( + claimRemoteCommitMainOutput(feeTargets, keyManager, commitments, remoteCommit.remotePerCommitmentPoint, tx, feeEstimator).copy( claimHtlcSuccessTxs = txes.toList.collect { case c: ClaimHtlcSuccessTx => c.tx }, claimHtlcTimeoutTxs = txes.toList.collect { case c: ClaimHtlcTimeoutTx => c.tx } ) @@ -625,11 +624,10 @@ object Helpers { * @param tx the remote commitment transaction that has just been published * @return a list of transactions (one per HTLC that we can claim) */ - def claimRemoteCommitMainOutput(keyManager: KeyManager, commitments: Commitments, remotePerCommitmentPoint: PublicKey, tx: Transaction, feeEstimator: FeeEstimator)(implicit log: LoggingAdapter): RemoteCommitPublished = { + def claimRemoteCommitMainOutput(feeTargets: FeeTargets, keyManager: KeyManager, commitments: Commitments, remotePerCommitmentPoint: PublicKey, tx: Transaction, feeEstimator: FeeEstimator)(implicit log: LoggingAdapter): RemoteCommitPublished = { val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(commitments.localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) - // no need to use a high fee rate for our main output (we are the only one who can spend it) - val feeratePerKwMain = feeEstimator.getFeeratePerKw(target = 6) + val feeratePerKwMain = feeEstimator.getFeeratePerKw(feeTargets.claimMainBlockTarget) val mainTx = generateTx("claim-p2wpkh-output")(Try { val claimMain = Transactions.makeClaimP2WPKHOutputTx(tx, Satoshi(commitments.localParams.dustLimitSatoshis), @@ -656,7 +654,7 @@ object Helpers { * * @return a [[RevokedCommitPublished]] object containing penalty transactions if the tx is a revoked commitment */ - def claimRevokedRemoteCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, tx: Transaction, db: ChannelsDb, feeEstimator: FeeEstimator)(implicit log: LoggingAdapter): Option[RevokedCommitPublished] = { + def claimRevokedRemoteCommitTxOutputs(feeTargets: FeeTargets, keyManager: KeyManager, commitments: Commitments, tx: Transaction, db: ChannelsDb, feeEstimator: FeeEstimator)(implicit log: LoggingAdapter): Option[RevokedCommitPublished] = { import commitments._ require(tx.txIn.size == 1, "commitment tx should have 1 input") val obscuredTxNumber = Transactions.decodeTxNumber(tx.txIn(0).sequence, tx.lockTime) @@ -675,8 +673,7 @@ object Helpers { val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint) - // no need to use a high fee rate for our main output (we are the only one who can spend it) - val feeratePerKwMain = feeEstimator.getFeeratePerKw(target = 6) + val feeratePerKwMain = feeEstimator.getFeeratePerKw(feeTargets.claimMainBlockTarget) // we need to use a high fee here for punishment txes because after a delay they can be spent by the counterparty val feeratePerKwPenalty = feeEstimator.getFeeratePerKw(target = 2) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index 894b45e979..b7b688681d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -65,7 +65,7 @@ object Transactions { case object OutputNotFound extends RuntimeException(s"output not found (probably trimmed)") with TxGenerationSkipped case object AmountBelowDustLimit extends RuntimeException(s"amount is below dust limit") with TxGenerationSkipped - case class FeeConf(fundingBlockTarget: Int, commitmentBlockTarget: Int, mutualCloseBlockTarget: Int) + case class FeeTargets(fundingBlockTarget: Int, commitmentBlockTarget: Int, mutualCloseBlockTarget: Int, claimMainBlockTarget: Int) // @formatter:on /** diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala index 71358814c5..3b1e6dff4a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala @@ -26,7 +26,7 @@ import fr.acinq.eclair.db._ import fr.acinq.eclair.db.sqlite._ import fr.acinq.eclair.io.Peer import fr.acinq.eclair.router.RouterConf -import fr.acinq.eclair.transactions.Transactions.FeeConf +import fr.acinq.eclair.transactions.Transactions.FeeTargets import fr.acinq.eclair.wire.{Color, NodeAddress} import scodec.bits.ByteVector import scala.concurrent.duration._ @@ -86,7 +86,7 @@ object TestConstants { pingInterval = 30 seconds, pingTimeout = 10 seconds, pingDisconnect = true, - feeTargets = FeeConf(6, 2, 2), + feeTargets = FeeTargets(6, 2, 2, 6), maxFeerateMismatch = 1.5, updateFeeMinDiffRatio = 0.1, autoReconnect = false, @@ -153,7 +153,7 @@ object TestConstants { pingInterval = 30 seconds, pingTimeout = 10 seconds, pingDisconnect = true, - feeTargets = FeeConf(6, 2, 2), + feeTargets = FeeTargets(6, 2, 2, 6), maxFeerateMismatch = 1.0, updateFeeMinDiffRatio = 0.1, autoReconnect = false, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 41452b239b..043b3dfd6b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -34,7 +34,7 @@ import fr.acinq.eclair.io.Peer import fr.acinq.eclair.payment._ import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.transactions.Transactions.{htlcSuccessWeight, htlcTimeoutWeight, weight2fee} -import fr.acinq.eclair.transactions.{IN, OUT} +import fr.acinq.eclair.transactions.{IN, OUT, Transactions} import fr.acinq.eclair.wire.{AnnouncementSignatures, ChannelUpdate, ClosingSigned, CommitSig, Error, FailureMessageCodecs, PermanentChannelFailure, RevokeAndAck, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFee, UpdateFulfillHtlc} import fr.acinq.eclair.{Globals, TestConstants, TestkitBaseClass, randomBytes32} import org.scalatest.{Outcome, Tag} @@ -1756,6 +1756,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { // in response to that, alice publishes its claim txes val claimTxes = for (i <- 0 until 4) yield alice2blockchain.expectMsgType[PublishAsap].tx + val claimMain = claimTxes(0) // in addition to its main output, alice can only claim 3 out of 4 htlcs, she can't do anything regarding the htlc sent by bob for which she does not have the preimage val amountClaimed = (for (claimHtlcTx <- claimTxes) yield { assert(claimHtlcTx.txIn.size == 1) @@ -1767,7 +1768,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { assert(amountClaimed == Satoshi(814880)) assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(bobCommitTx)) - assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(claimTxes(0))) // claim-main + assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(claimMain)) // claim-main assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) @@ -1777,6 +1778,12 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.isDefined) assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get.claimHtlcSuccessTxs.size == 1) assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get.claimHtlcTimeoutTxs.size == 2) + + // assert the feerate of the claim main is what we expect + val expectedFeeRate = feeEstimator.getFeeratePerKw(Alice.nodeParams.feeTargets.claimMainBlockTarget) + val expectedFee = Transactions.weight2fee(expectedFeeRate, Transactions.claimP2WPKHOutputWeight).toLong + val claimFee = claimMain.txIn.map(in => bobCommitTx.txOut(in.outPoint.index.toInt).amount.toLong).sum - claimMain.txOut.map(_.amount.toLong).sum + assert(claimFee == expectedFee) } test("recv BITCOIN_FUNDING_SPENT (their *next* commit w/ htlc)") { f => From de55d9229778033ec54d7a520f5a4fa8f81d4c85 Mon Sep 17 00:00:00 2001 From: Andrea Date: Sat, 20 Jul 2019 15:46:45 +0200 Subject: [PATCH 07/25] Add confirmation target block 144 --- eclair-core/src/main/resources/reference.conf | 3 ++- .../main/scala/fr/acinq/eclair/Setup.scala | 3 ++- .../fee/BitcoinCoreFeeProvider.scala | 4 +++- .../blockchain/fee/BitgoFeeProvider.scala | 3 ++- .../fee/EarnDotComFeeProvider.scala | 3 ++- .../eclair/blockchain/fee/FeeProvider.scala | 20 +++++++++++-------- .../blockchain/fee/SmoothFeeProvider.scala | 3 ++- .../fee/BitcoinCoreFeeProviderSpec.scala | 10 ++++++---- .../blockchain/fee/BitgoFeeProviderSpec.scala | 3 ++- .../fee/EarnDotComFeeProviderSpec.scala | 3 ++- .../fee/FallbackFeeProviderSpec.scala | 6 +++--- .../fee/SmoothFeeProviderSpec.scala | 12 +++++------ .../channel/states/e/NormalStateSpec.scala | 4 ++-- .../channel/states/f/ShutdownStateSpec.scala | 2 +- .../channel/states/h/ClosingStateSpec.scala | 2 +- 15 files changed, 48 insertions(+), 33 deletions(-) diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf index 4afbf55cc8..5fddaaaa04 100644 --- a/eclair-core/src/main/resources/reference.conf +++ b/eclair-core/src/main/resources/reference.conf @@ -37,10 +37,11 @@ eclair { 12 = 110000 36 = 50000 72 = 20000 + 144 = 15000 } } - // number of blocks to target when computing fees for each transaction type, possible values are: 1, 2, 6, 12, 36, 72 + // number of blocks to target when computing fees for each transaction type fee-targets { funding = 6 // target for the funding transaction commitment = 2 // target for the commitment transaction (used in force-close scenario) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala index 4a28c24770..92ca2dcb61 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala @@ -188,7 +188,8 @@ class Setup(datadir: File, blocks_6 = config.getLong("default-feerates.delay-blocks.6"), blocks_12 = config.getLong("default-feerates.delay-blocks.12"), blocks_36 = config.getLong("default-feerates.delay-blocks.36"), - blocks_72 = config.getLong("default-feerates.delay-blocks.72") + blocks_72 = config.getLong("default-feerates.delay-blocks.72"), + blocks_144 = config.getLong("default-feerates.delay-blocks.144") ) Globals.feeratesPerKB.set(confDefaultFeerates) Globals.feeratesPerKw.set(FeeratesPerKw(confDefaultFeerates)) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/BitcoinCoreFeeProvider.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/BitcoinCoreFeeProvider.scala index 12430937f6..b25bf1d900 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/BitcoinCoreFeeProvider.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/BitcoinCoreFeeProvider.scala @@ -46,13 +46,15 @@ class BitcoinCoreFeeProvider(rpcClient: BitcoinJsonRPCClient, defaultFeerates: F blocks_12 <- estimateSmartFee(12) blocks_36 <- estimateSmartFee(36) blocks_72 <- estimateSmartFee(72) + blocks_144 <- estimateSmartFee(144) } yield FeeratesPerKB( block_1 = if (block_1 > 0) block_1 else defaultFeerates.block_1, blocks_2 = if (blocks_2 > 0) blocks_2 else defaultFeerates.blocks_2, blocks_6 = if (blocks_6 > 0) blocks_6 else defaultFeerates.blocks_6, blocks_12 = if (blocks_12 > 0) blocks_12 else defaultFeerates.blocks_12, blocks_36 = if (blocks_36 > 0) blocks_36 else defaultFeerates.blocks_36, - blocks_72 = if (blocks_72 > 0) blocks_72 else defaultFeerates.blocks_72) + blocks_72 = if (blocks_72 > 0) blocks_72 else defaultFeerates.blocks_72, + blocks_144 = if (blocks_144 > 0) blocks_144 else defaultFeerates.blocks_144) } object BitcoinCoreFeeProvider { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/BitgoFeeProvider.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/BitgoFeeProvider.scala index 14c84d6d0e..97e1ed5fa3 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/BitgoFeeProvider.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/BitgoFeeProvider.scala @@ -72,6 +72,7 @@ object BitgoFeeProvider { blocks_6 = extractFeerate(feeRanges, 6), blocks_12 = extractFeerate(feeRanges, 12), blocks_36 = extractFeerate(feeRanges, 36), - blocks_72 = extractFeerate(feeRanges, 72)) + blocks_72 = extractFeerate(feeRanges, 72), + blocks_144 = extractFeerate(feeRanges, 144)) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/EarnDotComFeeProvider.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/EarnDotComFeeProvider.scala index b48f85ed5a..8dcc8982bd 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/EarnDotComFeeProvider.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/EarnDotComFeeProvider.scala @@ -76,6 +76,7 @@ object EarnDotComFeeProvider { blocks_6 = extractFeerate(feeRanges, 6), blocks_12 = extractFeerate(feeRanges, 12), blocks_36 = extractFeerate(feeRanges, 36), - blocks_72 = extractFeerate(feeRanges, 72)) + blocks_72 = extractFeerate(feeRanges, 72), + blocks_144 = extractFeerate(feeRanges, 144)) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeProvider.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeProvider.scala index 1db895c885..35e7b75169 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeProvider.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeProvider.scala @@ -30,8 +30,8 @@ trait FeeProvider { } // stores fee rate in satoshi/kb (1 kb = 1000 bytes) -case class FeeratesPerKB(block_1: Long, blocks_2: Long, blocks_6: Long, blocks_12: Long, blocks_36: Long, blocks_72: Long) { - require(block_1 > 0 && blocks_2 > 0 && blocks_6 > 0 && blocks_12 > 0 && blocks_36 > 0 && blocks_72 > 0, "all feerates must be strictly greater than 0") +case class FeeratesPerKB(block_1: Long, blocks_2: Long, blocks_6: Long, blocks_12: Long, blocks_36: Long, blocks_72: Long, blocks_144: Long) { + require(block_1 > 0 && blocks_2 > 0 && blocks_6 > 0 && blocks_12 > 0 && blocks_36 > 0 && blocks_72 > 0 && blocks_144 > 0, "all feerates must be strictly greater than 0") def feePerBlock(target: Int) = target match { case 1 => block_1 @@ -39,13 +39,14 @@ case class FeeratesPerKB(block_1: Long, blocks_2: Long, blocks_6: Long, blocks_1 case t if t <= 6 => blocks_6 case t if t <= 12 => blocks_12 case t if t <= 36 => blocks_36 - case _ => blocks_72 + case t if t <= 72 => blocks_72 + case _ => blocks_144 } } // stores fee rate in satoshi/kw (1 kw = 1000 weight units) -case class FeeratesPerKw(block_1: Long, blocks_2: Long, blocks_6: Long, blocks_12: Long, blocks_36: Long, blocks_72: Long) { - require(block_1 > 0 && blocks_2 > 0 && blocks_6 > 0 && blocks_12 > 0 && blocks_36 > 0 && blocks_72 > 0, "all feerates must be strictly greater than 0") +case class FeeratesPerKw(block_1: Long, blocks_2: Long, blocks_6: Long, blocks_12: Long, blocks_36: Long, blocks_72: Long, blocks_144: Long) { + require(block_1 > 0 && blocks_2 > 0 && blocks_6 > 0 && blocks_12 > 0 && blocks_36 > 0 && blocks_72 > 0 && blocks_144 > 0, "all feerates must be strictly greater than 0") def feePerBlock(target: Int) = target match { case 1 => block_1 @@ -53,7 +54,8 @@ case class FeeratesPerKw(block_1: Long, blocks_2: Long, blocks_6: Long, blocks_1 case t if t <= 6 => blocks_6 case t if t <= 12 => blocks_12 case t if t <= 36 => blocks_36 - case _ => blocks_72 + case t if t <= 72 => blocks_72 + case _ => blocks_144 } } @@ -64,7 +66,8 @@ object FeeratesPerKw { blocks_6 = feerateKB2Kw(feerates.blocks_6), blocks_12 = feerateKB2Kw(feerates.blocks_12), blocks_36 = feerateKB2Kw(feerates.blocks_36), - blocks_72 = feerateKB2Kw(feerates.blocks_72)) + blocks_72 = feerateKB2Kw(feerates.blocks_72), + blocks_144 = feerateKB2Kw(feerates.blocks_144)) /** * Used in tests @@ -78,6 +81,7 @@ object FeeratesPerKw { blocks_6 = feeratePerKw, blocks_12 = feeratePerKw, blocks_36 = feeratePerKw, - blocks_72 = feeratePerKw) + blocks_72 = feeratePerKw, + blocks_144 = feeratePerKw) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/SmoothFeeProvider.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/SmoothFeeProvider.scala index d805483880..35e61f31f9 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/SmoothFeeProvider.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/SmoothFeeProvider.scala @@ -47,5 +47,6 @@ object SmoothFeeProvider { blocks_6 = avg(rates.map(_.blocks_6)), blocks_12 = avg(rates.map(_.blocks_12)), blocks_36 = avg(rates.map(_.blocks_36)), - blocks_72 = avg(rates.map(_.blocks_72))) + blocks_72 = avg(rates.map(_.blocks_72)), + blocks_144 = avg(rates.map(_.blocks_144))) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/BitcoinCoreFeeProviderSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/BitcoinCoreFeeProviderSpec.scala index 0a671a648d..b129f52192 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/BitcoinCoreFeeProviderSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/BitcoinCoreFeeProviderSpec.scala @@ -88,7 +88,7 @@ class BitcoinCoreFeeProviderSpec extends TestKit(ActorSystem("test")) with Bitco port = config.getInt("bitcoind.rpcport")) // the regtest client doesn't have enough data to estimate fees yet, so it's suppose to fail - val regtestProvider = new BitcoinCoreFeeProvider(bitcoinClient, FeeratesPerKB(1, 2, 3, 4, 5, 6)) + val regtestProvider = new BitcoinCoreFeeProvider(bitcoinClient, FeeratesPerKB(1, 2, 3, 4, 5, 6, 7)) val sender = TestProbe() regtestProvider.getFeerates.pipeTo(sender.ref) assert(sender.expectMsgType[Failure].cause.asInstanceOf[RuntimeException].getMessage.contains("Insufficient data or no feerate found")) @@ -99,7 +99,8 @@ class BitcoinCoreFeeProviderSpec extends TestKit(ActorSystem("test")) with Bitco 6 -> 1300, 12 -> 1200, 36 -> 1100, - 72 -> 1000 + 72 -> 1000, + 144 -> 900 ) val ref = FeeratesPerKB( @@ -108,7 +109,8 @@ class BitcoinCoreFeeProviderSpec extends TestKit(ActorSystem("test")) with Bitco blocks_6 = fees(6), blocks_12 = fees(12), blocks_36 = fees(36), - blocks_72 = fees(72)) + blocks_72 = fees(72), + blocks_144 = fees(144)) val mockBitcoinClient = new BasicBitcoinJsonRPCClient( user = config.getString("bitcoind.rpcuser"), @@ -124,7 +126,7 @@ class BitcoinCoreFeeProviderSpec extends TestKit(ActorSystem("test")) with Bitco } } - val mockProvider = new BitcoinCoreFeeProvider(mockBitcoinClient, FeeratesPerKB(1, 2, 3, 4, 5, 6)) + val mockProvider = new BitcoinCoreFeeProvider(mockBitcoinClient, FeeratesPerKB(1, 2, 3, 4, 5, 6, 7)) mockProvider.getFeerates.pipeTo(sender.ref) assert(sender.expectMsgType[FeeratesPerKB] == ref) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/BitgoFeeProviderSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/BitgoFeeProviderSpec.scala index 263da95fd2..bfa1494e25 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/BitgoFeeProviderSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/BitgoFeeProviderSpec.scala @@ -60,7 +60,8 @@ class BitgoFeeProviderSpec extends FunSuite { blocks_6 = 105566, blocks_12 = 96254, blocks_36 = 71098, - blocks_72 = 68182) + blocks_72 = 68182, + blocks_144 = 16577) assert(feerates === ref) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/EarnDotComFeeProviderSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/EarnDotComFeeProviderSpec.scala index 4f1cfe3b48..9b3f953b07 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/EarnDotComFeeProviderSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/EarnDotComFeeProviderSpec.scala @@ -63,7 +63,8 @@ class EarnDotComFeeProviderSpec extends FunSuite with Logging { blocks_6 = 230 * 1000, blocks_12 = 140 * 1000, blocks_36 = 60 * 1000, - blocks_72 = 40 * 1000) + blocks_72 = 40 * 1000, + blocks_144 = 10 * 1000) assert(feerates === ref) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/FallbackFeeProviderSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/FallbackFeeProviderSpec.scala index bf70cdfd02..a742fa6eb2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/FallbackFeeProviderSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/FallbackFeeProviderSpec.scala @@ -43,7 +43,7 @@ class FallbackFeeProviderSpec extends FunSuite { } else Future.failed(new RuntimeException()) } - def dummyFeerates = FeeratesPerKB(1000 + Random.nextInt(10000), 1000 + Random.nextInt(10000), 1000 + Random.nextInt(10000), 1000 + Random.nextInt(10000), 1000 + Random.nextInt(10000), 1000 + Random.nextInt(10000)) + def dummyFeerates = FeeratesPerKB(1000 + Random.nextInt(10000), 1000 + Random.nextInt(10000), 1000 + Random.nextInt(10000), 1000 + Random.nextInt(10000), 1000 + Random.nextInt(10000), 1000 + Random.nextInt(10000), 1000 + Random.nextInt(10000)) def await[T](f: Future[T]): T = Await.result(f, 3 seconds) @@ -73,9 +73,9 @@ class FallbackFeeProviderSpec extends FunSuite { } test("ensure minimum feerate") { - val constantFeeProvider = new ConstantFeeProvider(FeeratesPerKB(1000, 1000, 1000, 1000, 1000, 1000)) + val constantFeeProvider = new ConstantFeeProvider(FeeratesPerKB(1000, 1000, 1000, 1000, 1000, 1000, 1000)) val fallbackFeeProvider = new FallbackFeeProvider(constantFeeProvider :: Nil, 2) - assert(await(fallbackFeeProvider.getFeerates) === FeeratesPerKB(2000, 2000, 2000, 2000, 2000, 2000)) + assert(await(fallbackFeeProvider.getFeerates) === FeeratesPerKB(2000, 2000, 2000, 2000, 2000, 2000, 1000)) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/SmoothFeeProviderSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/SmoothFeeProviderSpec.scala index 6c0e514ef4..a38546dc3b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/SmoothFeeProviderSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/SmoothFeeProviderSpec.scala @@ -26,11 +26,11 @@ import scala.concurrent.{Await, Future} class SmoothFeeProviderSpec extends FunSuite { test("smooth fee rates") { val rates = Array( - FeeratesPerKB(100, 200, 300, 400, 500, 600), - FeeratesPerKB(200, 300, 400, 500, 600, 700), - FeeratesPerKB(300, 400, 500, 600, 700, 800), - FeeratesPerKB(300, 400, 500, 600, 700, 800), - FeeratesPerKB(300, 400, 500, 600, 700, 800) + FeeratesPerKB(100, 200, 300, 400, 500, 600, 650), + FeeratesPerKB(200, 300, 400, 500, 600, 700, 750), + FeeratesPerKB(300, 400, 500, 600, 700, 800, 850), + FeeratesPerKB(300, 400, 500, 600, 700, 800, 850), + FeeratesPerKB(300, 400, 500, 600, 700, 800, 850) ) val provider = new FeeProvider { var index = 0 @@ -55,7 +55,7 @@ class SmoothFeeProviderSpec extends FunSuite { assert(rate1 == rates(0)) assert(rate2 == SmoothFeeProvider.smooth(Seq(rates(0), rates(1)))) assert(rate3 == SmoothFeeProvider.smooth(Seq(rates(0), rates(1), rates(2)))) - assert(rate3 == FeeratesPerKB(200, 300, 400, 500, 600, 700)) + assert(rate3 == FeeratesPerKB(200, 300, 400, 500, 600, 700, 750)) assert(rate4 == SmoothFeeProvider.smooth(Seq(rates(1), rates(2), rates(3)))) assert(rate5 == rates(4)) // since the last 3 values are the same } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 043b3dfd6b..395af2c021 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -1404,7 +1404,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { test("recv UpdateFee (local/remote feerates are too different)") { f => import f._ - feeEstimator.setFeerate(FeeratesPerKw(1000, 2000, 6000, 12000, 36000, 72000)) + feeEstimator.setFeerate(FeeratesPerKw(1000, 2000, 6000, 12000, 36000, 72000, 140000)) val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx val sender = TestProbe() // Alice will use $localFeeRate when performing the checks for update_fee @@ -1692,7 +1692,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { import f._ val sender = TestProbe() val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - val event = CurrentFeerates(FeeratesPerKw(100, 200, 600, 1200, 3600, 7200)) + val event = CurrentFeerates(FeeratesPerKw(100, 200, 600, 1200, 3600, 7200, 14400)) sender.send(alice, event) alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, event.feeratesPerKw.feePerBlock(Alice.nodeParams.feeTargets.commitmentBlockTarget))) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala index 050016816b..50a235729d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala @@ -646,7 +646,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { import f._ val sender = TestProbe() val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] - val event = CurrentFeerates(FeeratesPerKw(100, 200, 600, 1200, 3600, 7200)) + val event = CurrentFeerates(FeeratesPerKw(100, 200, 600, 1200, 3600, 7200, 14400)) sender.send(alice, event) alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, event.feeratesPerKw.feePerBlock(Alice.nodeParams.feeTargets.commitmentBlockTarget))) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala index e6511997e5..193caf0d07 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala @@ -143,7 +143,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { test("start fee negotiation from configured block target") { f => import f._ - feeEstimator.setFeerate(FeeratesPerKw(100, 250, 350, 450, 600, 800)) + feeEstimator.setFeerate(FeeratesPerKw(100, 250, 350, 450, 600, 800, 900)) val sender = TestProbe() // alice initiates a closing From 83763ef7c98dc3aa15ec80dcde8e93af5a049d75 Mon Sep 17 00:00:00 2001 From: Andrea Date: Sat, 20 Jul 2019 15:48:32 +0200 Subject: [PATCH 08/25] Fix bitgo fee provider test --- .../eclair/blockchain/fee/BitgoFeeProviderSpec.scala | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/BitgoFeeProviderSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/BitgoFeeProviderSpec.scala index bfa1494e25..6f789a34a7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/BitgoFeeProviderSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/BitgoFeeProviderSpec.scala @@ -18,9 +18,13 @@ package fr.acinq.eclair.blockchain.fee import akka.actor.ActorSystem import akka.util.Timeout +import com.softwaremill.sttp.okhttp.{OkHttpBackend, OkHttpFutureBackend} +import fr.acinq.bitcoin.Block import org.json4s.DefaultFormats import org.scalatest.FunSuite +import scala.concurrent.Await + /** * Created by PM on 27/01/2017. */ @@ -68,7 +72,11 @@ class BitgoFeeProviderSpec extends FunSuite { test("make sure API hasn't changed") { import scala.concurrent.duration._ implicit val system = ActorSystem() + implicit val ec = system.dispatcher + implicit val sttp = OkHttpFutureBackend() implicit val timeout = Timeout(30 seconds) + val bitgo = new BitgoFeeProvider(Block.LivenetGenesisBlock.hash) + assert(Await.result(bitgo.getFeerates, timeout.duration).block_1 > 0) } -} +} \ No newline at end of file From 7ec7e3adf204005be77006bba9b9c427d8f1666c Mon Sep 17 00:00:00 2001 From: Andrea Date: Sat, 20 Jul 2019 15:51:30 +0200 Subject: [PATCH 09/25] Add copyright header to FeeEstimator.scala --- .../eclair/blockchain/fee/FeeEstimator.scala | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala index 05f76d4a05..883bbb8216 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2019 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.blockchain.fee trait FeeEstimator { From f0649ddd1cb6b3f97e3c0a6e6236f41ac5ae88cb Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 22 Jul 2019 12:15:34 +0200 Subject: [PATCH 10/25] Reorg parametres order, pass `FeeEstimator` from Setup, move `FeeTargets` to fr.acinq.blockchain.fee.FeeEstimator. --- .../scala/fr/acinq/eclair/NodeParams.scala | 12 +++++----- .../main/scala/fr/acinq/eclair/Setup.scala | 7 +++++- .../eclair/blockchain/fee/FeeEstimator.scala | 4 +++- .../fr/acinq/eclair/channel/Channel.scala | 22 +++++++++---------- .../fr/acinq/eclair/channel/Commitments.scala | 4 ++-- .../fr/acinq/eclair/channel/Helpers.scala | 16 +++++++------- .../eclair/transactions/Transactions.scala | 1 - .../scala/fr/acinq/eclair/StartupSpec.scala | 2 +- .../scala/fr/acinq/eclair/TestConstants.scala | 5 +++-- .../channel/states/h/ClosingStateSpec.scala | 2 +- 10 files changed, 40 insertions(+), 35 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala index 9536e2cdfd..db1dfbec0f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala @@ -20,19 +20,20 @@ import java.io.File import java.net.InetSocketAddress import java.nio.file.Files import java.util.concurrent.TimeUnit + import com.typesafe.config.{Config, ConfigFactory} import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.{Block, ByteVector32} import fr.acinq.eclair.NodeParams.WatcherType -import fr.acinq.eclair.blockchain.fee.FeeEstimator +import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeTargets} import fr.acinq.eclair.channel.Channel import fr.acinq.eclair.crypto.KeyManager import fr.acinq.eclair.db._ import fr.acinq.eclair.router.RouterConf import fr.acinq.eclair.tor.Socks5ProxyParams -import fr.acinq.eclair.transactions.Transactions.FeeTargets import fr.acinq.eclair.wire.{Color, NodeAddress} import scodec.bits.ByteVector + import scala.collection.JavaConversions._ import scala.concurrent.duration.FiniteDuration @@ -125,7 +126,7 @@ object NodeParams { } } - def makeNodeParams(config: Config, keyManager: KeyManager, torAddress_opt: Option[NodeAddress], database: Databases): NodeParams = { + def makeNodeParams(config: Config, keyManager: KeyManager, torAddress_opt: Option[NodeAddress], database: Databases, feeEstimator: FeeEstimator): NodeParams = { val chain = config.getString("chain") val chainHash = makeChainHash(chain) @@ -200,10 +201,7 @@ object NodeParams { toRemoteDelayBlocks = config.getInt("to-remote-delay-blocks"), maxToLocalDelayBlocks = config.getInt("max-to-local-delay-blocks"), minDepthBlocks = config.getInt("mindepth-blocks"), - feeEstimator = new FeeEstimator { - override def getFeeratePerKb(target: Int): Long = Globals.feeratesPerKB.get().feePerBlock(target) - override def getFeeratePerKw(target: Int): Long = Globals.feeratesPerKw.get().feePerBlock(target) - }, + feeEstimator = feeEstimator, feeBaseMsat = config.getInt("fee-base-msat"), feeProportionalMillionth = config.getInt("fee-proportional-millionths"), reserveToFundingRatio = config.getDouble("reserve-to-funding-ratio"), diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala index 92ca2dcb61..e6bc76d8b1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala @@ -96,7 +96,12 @@ class Setup(datadir: File, case None => Databases.sqliteJDBC(chaindir) } - val nodeParams = NodeParams.makeNodeParams(config, keyManager, initTor(), database) + val feeEstimator = new FeeEstimator { + override def getFeeratePerKb(target: Int): Long = Globals.feeratesPerKB.get().feePerBlock(target) + override def getFeeratePerKw(target: Int): Long = Globals.feeratesPerKw.get().feePerBlock(target) + } + + val nodeParams = NodeParams.makeNodeParams(config, keyManager, initTor(), database, feeEstimator) val serverBindingAddress = new InetSocketAddress( config.getString("server.binding-ip"), diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala index 883bbb8216..6123935818 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala @@ -21,4 +21,6 @@ trait FeeEstimator { def getFeeratePerKb(target: Int) : Long def getFeeratePerKw(target: Int) : Long -} \ No newline at end of file +} + +case class FeeTargets(fundingBlockTarget: Int, commitmentBlockTarget: Int, mutualCloseBlockTarget: Int, claimMainBlockTarget: Int) \ No newline at end of file diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 55fff55426..0b89c40a8a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -681,7 +681,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId } case Event(fee: UpdateFee, d: DATA_NORMAL) => - Try(Commitments.receiveFee(nodeParams.feeTargets, d.commitments, fee, nodeParams.maxFeerateMismatch, nodeParams.feeEstimator)) match { + Try(Commitments.receiveFee(d.commitments, fee, nodeParams.maxFeerateMismatch, nodeParams.feeEstimator, nodeParams.feeTargets)) match { case Success(commitments1) => stay using d.copy(commitments = commitments1) case Failure(cause) => handleLocalError(cause, d, Some(fee)) } @@ -1040,7 +1040,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId } case Event(fee: UpdateFee, d: DATA_SHUTDOWN) => - Try(Commitments.receiveFee(nodeParams.feeTargets, d.commitments, fee, nodeParams.maxFeerateMismatch, nodeParams.feeEstimator)) match { + Try(Commitments.receiveFee(d.commitments, fee, nodeParams.maxFeerateMismatch, nodeParams.feeEstimator, nodeParams.feeTargets)) match { case Success(commitments1) => stay using d.copy(commitments = commitments1) case Failure(cause) => handleLocalError(cause, d, Some(fee)) } @@ -1214,19 +1214,19 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId log.info(s"got valid payment preimage, recalculating transactions to redeem the corresponding htlc on-chain") val localCommitPublished1 = d.localCommitPublished.map { case localCommitPublished => - val localCommitPublished1 = Helpers.Closing.claimCurrentLocalCommitTxOutputs(nodeParams.feeTargets, keyManager, commitments1, localCommitPublished.commitTx, nodeParams.feeEstimator) + val localCommitPublished1 = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, commitments1, localCommitPublished.commitTx, nodeParams.feeEstimator, nodeParams.feeTargets) doPublish(localCommitPublished1) localCommitPublished1 } val remoteCommitPublished1 = d.remoteCommitPublished.map { case remoteCommitPublished => - val remoteCommitPublished1 = Helpers.Closing.claimRemoteCommitTxOutputs(nodeParams.feeTargets, keyManager, commitments1, commitments1.remoteCommit, remoteCommitPublished.commitTx, nodeParams.feeEstimator) + val remoteCommitPublished1 = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, commitments1, commitments1.remoteCommit, remoteCommitPublished.commitTx, nodeParams.feeEstimator, nodeParams.feeTargets) doPublish(remoteCommitPublished1) remoteCommitPublished1 } val nextRemoteCommitPublished1 = d.nextRemoteCommitPublished.map { case remoteCommitPublished => - val remoteCommitPublished1 = Helpers.Closing.claimRemoteCommitTxOutputs(nodeParams.feeTargets, keyManager, commitments1, commitments1.remoteCommit, remoteCommitPublished.commitTx, nodeParams.feeEstimator) + val remoteCommitPublished1 = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, commitments1, commitments1.remoteCommit, remoteCommitPublished.commitTx, nodeParams.feeEstimator, nodeParams.feeTargets) doPublish(remoteCommitPublished1) remoteCommitPublished1 } @@ -1906,7 +1906,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId } else { val commitTx = d.commitments.localCommit.publishableTxs.commitTx.tx - val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(nodeParams.feeTargets, keyManager, d.commitments, commitTx, nodeParams.feeEstimator) + val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, d.commitments, commitTx, nodeParams.feeEstimator, nodeParams.feeTargets) doPublish(localCommitPublished) val nextData = d match { @@ -1981,7 +1981,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId log.warning(s"they published their current commit in txid=${commitTx.txid}") require(commitTx.txid == d.commitments.remoteCommit.txid, "txid mismatch") - val remoteCommitPublished = Helpers.Closing.claimRemoteCommitTxOutputs(nodeParams.feeTargets, keyManager, d.commitments, d.commitments.remoteCommit, commitTx, nodeParams.feeEstimator) + val remoteCommitPublished = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, d.commitments, d.commitments.remoteCommit, commitTx, nodeParams.feeEstimator, nodeParams.feeTargets) doPublish(remoteCommitPublished) val nextData = d match { @@ -1998,7 +1998,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId log.warning(s"they published their future commit (because we asked them to) in txid=${commitTx.txid}") // if we are in this state, then this field is defined val remotePerCommitmentPoint = d.remoteChannelReestablish.myCurrentPerCommitmentPoint.get - val remoteCommitPublished = Helpers.Closing.claimRemoteCommitMainOutput(nodeParams.feeTargets, keyManager, d.commitments, remotePerCommitmentPoint, commitTx, nodeParams.feeEstimator) + val remoteCommitPublished = Helpers.Closing.claimRemoteCommitMainOutput(keyManager, d.commitments, remotePerCommitmentPoint, commitTx, nodeParams.feeEstimator, nodeParams.feeTargets) val nextData = DATA_CLOSING(d.commitments, fundingTx = None, waitingSince = now, Nil, futureRemoteCommitPublished = Some(remoteCommitPublished)) doPublish(remoteCommitPublished) @@ -2011,7 +2011,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId val remoteCommit = d.commitments.remoteNextCommitInfo.left.get.nextRemoteCommit require(commitTx.txid == remoteCommit.txid, "txid mismatch") - val remoteCommitPublished = Helpers.Closing.claimRemoteCommitTxOutputs(nodeParams.feeTargets, keyManager, d.commitments, remoteCommit, commitTx, nodeParams.feeEstimator) + val remoteCommitPublished = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, d.commitments, remoteCommit, commitTx, nodeParams.feeEstimator, nodeParams.feeTargets) doPublish(remoteCommitPublished) val nextData = d match { @@ -2044,7 +2044,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId def handleRemoteSpentOther(tx: Transaction, d: HasCommitments) = { log.warning(s"funding tx spent in txid=${tx.txid}") - Helpers.Closing.claimRevokedRemoteCommitTxOutputs(nodeParams.feeTargets, keyManager, d.commitments, tx, nodeParams.db.channels, nodeParams.feeEstimator) match { + Helpers.Closing.claimRevokedRemoteCommitTxOutputs(keyManager, d.commitments, tx, nodeParams.db.channels, nodeParams.feeEstimator, nodeParams.feeTargets) match { case Some(revokedCommitPublished) => log.warning(s"txid=${tx.txid} was a revoked commitment, publishing the penalty tx") val exc = FundingTxSpent(d.channelId, tx) @@ -2091,7 +2091,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId // let's try to spend our current local tx val commitTx = d.commitments.localCommit.publishableTxs.commitTx.tx - val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(nodeParams.feeTargets, keyManager, d.commitments, commitTx, nodeParams.feeEstimator) + val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, d.commitments, commitTx, nodeParams.feeEstimator, nodeParams.feeTargets) doPublish(localCommitPublished) goto(ERR_INFORMATION_LEAK) sending error diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index d15d9fa064..e6dbb5ce95 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -19,7 +19,7 @@ package fr.acinq.eclair.channel import akka.event.LoggingAdapter import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey, sha256} import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, Satoshi} -import fr.acinq.eclair.blockchain.fee.FeeEstimator +import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeTargets} import fr.acinq.eclair.crypto.{Generators, KeyManager, ShaChain, Sphinx} import fr.acinq.eclair.payment._ import fr.acinq.eclair.transactions.Transactions._ @@ -311,7 +311,7 @@ object Commitments { (commitments1, fee) } - def receiveFee(feeTargets: FeeTargets, commitments: Commitments, fee: UpdateFee, maxFeerateMismatch: Double, feeEstimator: FeeEstimator): Commitments = { + def receiveFee(commitments: Commitments, fee: UpdateFee, maxFeerateMismatch: Double, feeEstimator: FeeEstimator, feeTargets: FeeTargets): Commitments = { if (commitments.localParams.isFunder) { throw FundeeCannotSendUpdateFee(commitments.channelId) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 71982f39af..8377f88876 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -21,7 +21,7 @@ import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey, ripemd160, sha256} import fr.acinq.bitcoin.Script._ import fr.acinq.bitcoin.{OutPoint, _} import fr.acinq.eclair.blockchain.EclairWallet -import fr.acinq.eclair.blockchain.fee.FeeEstimator +import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeTargets} import fr.acinq.eclair.channel.Channel.REFRESH_CHANNEL_UPDATE_INTERVAL import fr.acinq.eclair.crypto.{Generators, KeyManager} import fr.acinq.eclair.db.ChannelsDb @@ -424,7 +424,7 @@ object Helpers { } } - def firstClosingFee(feeratePerKw: Long, commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector)(implicit log: LoggingAdapter): Satoshi = { + def firstClosingFee(commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feeratePerKw: Long)(implicit log: LoggingAdapter): Satoshi = { import commitments._ // this is just to estimate the weight, it depends on size of the pubkey scripts val dummyClosingTx = Transactions.makeClosingTx(commitInput, localScriptPubkey, remoteScriptPubkey, localParams.isFunder, Satoshi(0), Satoshi(0), localCommit.spec) @@ -437,7 +437,7 @@ object Helpers { // we "MUST set fee_satoshis less than or equal to the base fee of the final commitment transaction" val blockTarget = Math.max(feeTargets.mutualCloseBlockTarget, feeTargets.commitmentBlockTarget) val feeratePerKw = feeEstimator.getFeeratePerKw(blockTarget) - firstClosingFee(feeratePerKw, commitments, localScriptPubkey, remoteScriptPubkey) + firstClosingFee(commitments, localScriptPubkey, remoteScriptPubkey, feeratePerKw) } def nextClosingFee(localClosingFee: Satoshi, remoteClosingFee: Satoshi): Satoshi = ((localClosingFee + remoteClosingFee) / 4) * 2 @@ -496,7 +496,7 @@ object Helpers { * @param commitments our commitment data, which include payment preimages * @return a list of transactions (one per HTLC that we can claim) */ - def claimCurrentLocalCommitTxOutputs(feeTargets: FeeTargets, keyManager: KeyManager, commitments: Commitments, tx: Transaction, feeEstimator: FeeEstimator)(implicit log: LoggingAdapter): LocalCommitPublished = { + def claimCurrentLocalCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, tx: Transaction, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): LocalCommitPublished = { import commitments._ require(localCommit.publishableTxs.commitTx.tx.txid == tx.txid, "txid mismatch, provided tx is not the current local commit tx") @@ -567,7 +567,7 @@ object Helpers { * @param tx the remote commitment transaction that has just been published * @return a list of transactions (one per HTLC that we can claim) */ - def claimRemoteCommitTxOutputs(feeTargets: FeeTargets, keyManager: KeyManager, commitments: Commitments, remoteCommit: RemoteCommit, tx: Transaction, feeEstimator: FeeEstimator)(implicit log: LoggingAdapter): RemoteCommitPublished = { + def claimRemoteCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, remoteCommit: RemoteCommit, tx: Transaction, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): RemoteCommitPublished = { import commitments.{commitInput, localParams, remoteParams} require(remoteCommit.txid == tx.txid, "txid mismatch, provided tx is not the current remote commit tx") val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, remoteCommit.index, localParams, remoteParams, commitInput, remoteCommit.remotePerCommitmentPoint, remoteCommit.spec) @@ -607,7 +607,7 @@ object Helpers { }) }.toSeq.flatten - claimRemoteCommitMainOutput(feeTargets, keyManager, commitments, remoteCommit.remotePerCommitmentPoint, tx, feeEstimator).copy( + claimRemoteCommitMainOutput(keyManager, commitments, remoteCommit.remotePerCommitmentPoint, tx, feeEstimator, feeTargets).copy( claimHtlcSuccessTxs = txes.toList.collect { case c: ClaimHtlcSuccessTx => c.tx }, claimHtlcTimeoutTxs = txes.toList.collect { case c: ClaimHtlcTimeoutTx => c.tx } ) @@ -624,7 +624,7 @@ object Helpers { * @param tx the remote commitment transaction that has just been published * @return a list of transactions (one per HTLC that we can claim) */ - def claimRemoteCommitMainOutput(feeTargets: FeeTargets, keyManager: KeyManager, commitments: Commitments, remotePerCommitmentPoint: PublicKey, tx: Transaction, feeEstimator: FeeEstimator)(implicit log: LoggingAdapter): RemoteCommitPublished = { + def claimRemoteCommitMainOutput(keyManager: KeyManager, commitments: Commitments, remotePerCommitmentPoint: PublicKey, tx: Transaction, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): RemoteCommitPublished = { val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(commitments.localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) val feeratePerKwMain = feeEstimator.getFeeratePerKw(feeTargets.claimMainBlockTarget) @@ -654,7 +654,7 @@ object Helpers { * * @return a [[RevokedCommitPublished]] object containing penalty transactions if the tx is a revoked commitment */ - def claimRevokedRemoteCommitTxOutputs(feeTargets: FeeTargets, keyManager: KeyManager, commitments: Commitments, tx: Transaction, db: ChannelsDb, feeEstimator: FeeEstimator)(implicit log: LoggingAdapter): Option[RevokedCommitPublished] = { + def claimRevokedRemoteCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, tx: Transaction, db: ChannelsDb, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): Option[RevokedCommitPublished] = { import commitments._ require(tx.txIn.size == 1, "commitment tx should have 1 input") val obscuredTxNumber = Transactions.decodeTxNumber(tx.txIn(0).sequence, tx.lockTime) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index b7b688681d..0febeaab9e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -65,7 +65,6 @@ object Transactions { case object OutputNotFound extends RuntimeException(s"output not found (probably trimmed)") with TxGenerationSkipped case object AmountBelowDustLimit extends RuntimeException(s"amount is below dust limit") with TxGenerationSkipped - case class FeeTargets(fundingBlockTarget: Int, commitmentBlockTarget: Int, mutualCloseBlockTarget: Int, claimMainBlockTarget: Int) // @formatter:on /** diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/StartupSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/StartupSpec.scala index 1a00b51d64..da1edf35f7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/StartupSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/StartupSpec.scala @@ -43,7 +43,7 @@ class StartupSpec extends FunSuite { val keyManager = new LocalKeyManager(seed = randomBytes32, chainHash = Block.TestnetGenesisBlock.hash) // try to create a NodeParams instance with a conf that contains an illegal alias - val nodeParamsAttempt = Try(NodeParams.makeNodeParams(conf, keyManager, None, TestConstants.inMemoryDb())) + val nodeParamsAttempt = Try(NodeParams.makeNodeParams(conf, keyManager, None, TestConstants.inMemoryDb(), new TestConstants.TestFeeEstimator)) assert(nodeParamsAttempt.isFailure && nodeParamsAttempt.failed.get.getMessage.contains("alias, too long")) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala index 3b1e6dff4a..66c7f5e529 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala @@ -17,18 +17,19 @@ package fr.acinq.eclair import java.sql.{Connection, DriverManager} + import fr.acinq.bitcoin.Crypto.PrivateKey import fr.acinq.bitcoin.{Block, ByteVector32, Script} import fr.acinq.eclair.NodeParams.BITCOIND -import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeratesPerKw} +import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeTargets, FeeratesPerKw} import fr.acinq.eclair.crypto.LocalKeyManager import fr.acinq.eclair.db._ import fr.acinq.eclair.db.sqlite._ import fr.acinq.eclair.io.Peer import fr.acinq.eclair.router.RouterConf -import fr.acinq.eclair.transactions.Transactions.FeeTargets import fr.acinq.eclair.wire.{Color, NodeAddress} import scodec.bits.ByteVector + import scala.concurrent.duration._ /** diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala index 193caf0d07..0a52d2d6ec 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala @@ -155,7 +155,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val closing = alice2bob.expectMsgType[ClosingSigned] val aliceData = alice.stateData.asInstanceOf[DATA_NEGOTIATING] val mutualClosingFeeRate = feeEstimator.getFeeratePerKw(Alice.nodeParams.feeTargets.mutualCloseBlockTarget) - val expectedFirstProposedFee = Closing.firstClosingFee(mutualClosingFeeRate, aliceData.commitments, aliceData.localShutdown.scriptPubKey, aliceData.remoteShutdown.scriptPubKey)(akka.event.NoLogging) + val expectedFirstProposedFee = Closing.firstClosingFee(aliceData.commitments, aliceData.localShutdown.scriptPubKey, aliceData.remoteShutdown.scriptPubKey, mutualClosingFeeRate)(akka.event.NoLogging) assert(Alice.nodeParams.feeTargets.mutualCloseBlockTarget == 2 && mutualClosingFeeRate == 250) assert(closing.feeSatoshis == expectedFirstProposedFee.amount) } From a04eed8406a924ba1226303ee5f10e17edd63315 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 23 Jul 2019 10:31:48 +0200 Subject: [PATCH 11/25] Formatting --- .../main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala | 1 + eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala | 1 + 2 files changed, 2 insertions(+) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala index 6123935818..d896d39459 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala @@ -19,6 +19,7 @@ package fr.acinq.eclair.blockchain.fee trait FeeEstimator { def getFeeratePerKb(target: Int) : Long + def getFeeratePerKw(target: Int) : Long } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala index 66c7f5e529..10d6b8ce30 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala @@ -44,6 +44,7 @@ object TestConstants { private var currentFeerates = FeeratesPerKw.single(feeratePerKw) override def getFeeratePerKb(target: Int): Long = feerateKw2KB(currentFeerates.feePerBlock(target)) + override def getFeeratePerKw(target: Int): Long = currentFeerates.feePerBlock(target) def setFeerate(feeratesPerKw: FeeratesPerKw): Unit = { From 45100ad8b1229df823ddf5e6bd0232302b814e31 Mon Sep 17 00:00:00 2001 From: araspitzu Date: Tue, 23 Jul 2019 12:30:44 +0200 Subject: [PATCH 12/25] Update eclair-core/src/main/resources/reference.conf Add a warning to commitment confirmation target configuration key Co-Authored-By: Pierre-Marie Padiou --- eclair-core/src/main/resources/reference.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf index 5fddaaaa04..06afec7b84 100644 --- a/eclair-core/src/main/resources/reference.conf +++ b/eclair-core/src/main/resources/reference.conf @@ -44,7 +44,7 @@ eclair { // number of blocks to target when computing fees for each transaction type fee-targets { funding = 6 // target for the funding transaction - commitment = 2 // target for the commitment transaction (used in force-close scenario) + commitment = 2 // target for the commitment transaction (used in force-close scenario) *do not change this unless you know what you are doing* mutual-close = 12 // target for the mutual close transaction claim-main = 6 // target for the claim main transaction (tx that spends main channel output back to wallet) } From 7e31ebbf7e00704fb11120ab8b61c9e291d35e42 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 23 Jul 2019 12:43:51 +0200 Subject: [PATCH 13/25] Use underlyingActor to access `NodeParams` and feeEstimator during test --- .../channel/states/h/ClosingStateSpec.scala | 14 ++++++++------ .../scala/fr/acinq/eclair/io/PeerSpec.scala | 19 ++++++++----------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala index 0a52d2d6ec..cbe83bc3d0 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala @@ -46,7 +46,7 @@ import scala.concurrent.duration._ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { - case class FixtureParam(alice: TestFSMRef[State, Data, Channel], bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe, bob2blockchain: TestProbe, relayerA: TestProbe, relayerB: TestProbe, channelUpdateListener: TestProbe, bobCommitTxes: List[PublishableTxs], feeEstimator: TestFeeEstimator) + case class FixtureParam(alice: TestFSMRef[State, Data, Channel], bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe, bob2blockchain: TestProbe, relayerA: TestProbe, relayerB: TestProbe, channelUpdateListener: TestProbe, bobCommitTxes: List[PublishableTxs]) override def withFixture(test: OneArgTest): Outcome = { val setup = init() @@ -83,7 +83,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { bob2blockchain.expectMsgType[WatchConfirmed] awaitCond(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED) awaitCond(bob.stateName == WAIT_FOR_FUNDING_CONFIRMED) - withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayerA, relayerB, channelUpdateListener, Nil, feeEstimator))) + withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayerA, relayerB, channelUpdateListener, Nil))) } } else { within(30 seconds) { @@ -105,7 +105,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { awaitCond(alice.stateName == NORMAL) awaitCond(bob.stateName == NORMAL) - withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayerA, relayerB, channelUpdateListener, bobCommitTxes, feeEstimator))) + withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayerA, relayerB, channelUpdateListener, bobCommitTxes))) } } } @@ -143,7 +143,8 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { test("start fee negotiation from configured block target") { f => import f._ - feeEstimator.setFeerate(FeeratesPerKw(100, 250, 350, 450, 600, 800, 900)) + val nodeParams = alice.underlyingActor.nodeParams + nodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw(100, 250, 350, 450, 600, 800, 900)) val sender = TestProbe() // alice initiates a closing @@ -154,7 +155,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { bob2alice.forward(alice) val closing = alice2bob.expectMsgType[ClosingSigned] val aliceData = alice.stateData.asInstanceOf[DATA_NEGOTIATING] - val mutualClosingFeeRate = feeEstimator.getFeeratePerKw(Alice.nodeParams.feeTargets.mutualCloseBlockTarget) + val mutualClosingFeeRate = nodeParams.feeEstimator.getFeeratePerKw(nodeParams.feeTargets.mutualCloseBlockTarget) val expectedFirstProposedFee = Closing.firstClosingFee(aliceData.commitments, aliceData.localShutdown.scriptPubKey, aliceData.remoteShutdown.scriptPubKey, mutualClosingFeeRate)(akka.event.NoLogging) assert(Alice.nodeParams.feeTargets.mutualCloseBlockTarget == 2 && mutualClosingFeeRate == 250) assert(closing.feeSatoshis == expectedFirstProposedFee.amount) @@ -331,7 +332,8 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { bob2alice.forward(alice) // agreeing on a closing fee val aliceCloseFee = alice2bob.expectMsgType[ClosingSigned].feeSatoshis - feeEstimator.setFeerate(FeeratesPerKw.single(100)) + val nodeParams = alice.underlyingActor.nodeParams + nodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(100)) alice2bob.forward(bob) val bobCloseFee = bob2alice.expectMsgType[ClosingSigned].feeSatoshis bob2alice.forward(alice) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala index 1046cd8624..41da828386 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala @@ -16,9 +16,6 @@ package fr.acinq.eclair.io -import java.net.{Inet4Address, InetSocketAddress} - -import akka.actor.{ActorRef, ActorSystem, FSM, PoisonPill, Terminated} import java.net.{Inet4Address, InetAddress, InetSocketAddress, ServerSocket} import akka.actor.FSM.{CurrentState, SubscribeTransitionCallBack, Transition} @@ -29,7 +26,7 @@ import fr.acinq.bitcoin.{MilliSatoshi, Satoshi} import fr.acinq.eclair.TestConstants._ import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.{EclairWallet, TestWallet} -import fr.acinq.eclair.channel.{ChannelCreated, Data, HasCommitments, State} +import fr.acinq.eclair.channel.{ChannelCreated, HasCommitments} import fr.acinq.eclair.crypto.TransportHandler import fr.acinq.eclair.io.Peer._ import fr.acinq.eclair.router.RoutingSyncSpec.makeFakeRoutingInfo @@ -51,11 +48,10 @@ class PeerSpec extends TestkitBaseClass { val updates = (fakeRoutingInfo.map(_._2) ++ fakeRoutingInfo.map(_._3)).toList val nodes = (fakeRoutingInfo.map(_._4) ++ fakeRoutingInfo.map(_._5)).toList - case class FixtureParam(remoteNodeId: PublicKey, authenticator: TestProbe, watcher: TestProbe, router: TestProbe, relayer: TestProbe, connection: TestProbe, transport: TestProbe, peer: TestFSMRef[Peer.State, Peer.Data, Peer], feeEstimator: TestFeeEstimator) + case class FixtureParam(remoteNodeId: PublicKey, authenticator: TestProbe, watcher: TestProbe, router: TestProbe, relayer: TestProbe, connection: TestProbe, transport: TestProbe, peer: TestFSMRef[Peer.State, Peer.Data, Peer]) override protected def withFixture(test: OneArgTest): Outcome = { - val feeEstimator = new TestFeeEstimator - val aParams = Alice.nodeParams.copy(feeEstimator = feeEstimator) + val aParams = Alice.nodeParams val aliceParams = test.tags.contains("with_node_announcements") match { case true => val bobAnnouncement = NodeAnnouncement(randomBytes64, ByteVector.empty, 1, Bob.nodeParams.nodeId, Color(100.toByte, 200.toByte, 300.toByte), "node-alias", fakeIPAddress :: Nil) @@ -70,10 +66,10 @@ class PeerSpec extends TestkitBaseClass { val relayer = TestProbe() val connection = TestProbe() val transport = TestProbe() - val wallet: EclairWallet = new TestWallet + val wallet: EclairWallet = new TestWallet() val remoteNodeId = Bob.nodeParams.nodeId val peer: TestFSMRef[Peer.State, Peer.Data, Peer] = TestFSMRef(new Peer(aliceParams, remoteNodeId, authenticator.ref, watcher.ref, router.ref, relayer.ref, wallet)) - withFixture(test.toNoArgTest(FixtureParam(remoteNodeId, authenticator, watcher, router, relayer, connection, transport, peer, feeEstimator))) + withFixture(test.toNoArgTest(FixtureParam(remoteNodeId, authenticator, watcher, router, relayer, connection, transport, peer))) } def connect(remoteNodeId: PublicKey, authenticator: TestProbe, watcher: TestProbe, router: TestProbe, relayer: TestProbe, connection: TestProbe, transport: TestProbe, peer: ActorRef, channels: Set[HasCommitments] = Set.empty): Unit = { @@ -253,9 +249,10 @@ class PeerSpec extends TestkitBaseClass { probe.send(peer, Peer.OpenChannel(remoteNodeId, Satoshi(12300), MilliSatoshi(0), None, None, None)) awaitCond(peer.stateData.channels.nonEmpty) + val nodeParams = peer.underlyingActor.nodeParams val channelCreated = probe.expectMsgType[ChannelCreated] - assert(channelCreated.initialFeeratePerKw == feeEstimator.getFeeratePerKw(Alice.nodeParams.feeTargets.commitmentBlockTarget)) - assert(channelCreated.fundingTxFeeratePerKw.get == feeEstimator.getFeeratePerKw(Alice.nodeParams.feeTargets.fundingBlockTarget)) + assert(channelCreated.initialFeeratePerKw == nodeParams.feeEstimator.getFeeratePerKw(nodeParams.feeTargets.commitmentBlockTarget)) + assert(channelCreated.fundingTxFeeratePerKw.get == nodeParams.feeEstimator.getFeeratePerKw(nodeParams.feeTargets.fundingBlockTarget)) } test("reply to ping") { f => From c66b3b81b442e36026910523d1981da40190ac68 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 23 Jul 2019 12:58:18 +0200 Subject: [PATCH 14/25] Change position order of `receiveFee` parameters --- .../src/main/scala/fr/acinq/eclair/channel/Channel.scala | 4 ++-- .../src/main/scala/fr/acinq/eclair/channel/Commitments.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 0b89c40a8a..fb65dbbdab 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -681,7 +681,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId } case Event(fee: UpdateFee, d: DATA_NORMAL) => - Try(Commitments.receiveFee(d.commitments, fee, nodeParams.maxFeerateMismatch, nodeParams.feeEstimator, nodeParams.feeTargets)) match { + Try(Commitments.receiveFee(d.commitments, nodeParams.feeEstimator, nodeParams.feeTargets, fee, nodeParams.maxFeerateMismatch)) match { case Success(commitments1) => stay using d.copy(commitments = commitments1) case Failure(cause) => handleLocalError(cause, d, Some(fee)) } @@ -1040,7 +1040,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId } case Event(fee: UpdateFee, d: DATA_SHUTDOWN) => - Try(Commitments.receiveFee(d.commitments, fee, nodeParams.maxFeerateMismatch, nodeParams.feeEstimator, nodeParams.feeTargets)) match { + Try(Commitments.receiveFee(d.commitments, nodeParams.feeEstimator, nodeParams.feeTargets, fee, nodeParams.maxFeerateMismatch)) match { case Success(commitments1) => stay using d.copy(commitments = commitments1) case Failure(cause) => handleLocalError(cause, d, Some(fee)) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index e6dbb5ce95..f1045a1cb9 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -311,7 +311,7 @@ object Commitments { (commitments1, fee) } - def receiveFee(commitments: Commitments, fee: UpdateFee, maxFeerateMismatch: Double, feeEstimator: FeeEstimator, feeTargets: FeeTargets): Commitments = { + def receiveFee(commitments: Commitments, feeEstimator: FeeEstimator, feeTargets: FeeTargets, fee: UpdateFee, maxFeerateMismatch: Double): Commitments = { if (commitments.localParams.isFunder) { throw FundeeCannotSendUpdateFee(commitments.channelId) } From 1c219592f316826c8fa97678fdf8d9b6b85d5cdc Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 23 Jul 2019 13:40:15 +0200 Subject: [PATCH 15/25] Use feeEstimator from underlyingActor --- .../states/StateTestsHelperMethods.scala | 13 +++++-------- .../channel/states/e/NormalStateSpec.scala | 18 +++++++++++------- .../channel/states/f/ShutdownStateSpec.scala | 2 +- .../states/g/NegotiatingStateSpec.scala | 19 ++++++++++++++++--- .../channel/states/h/ClosingStateSpec.scala | 2 +- 5 files changed, 34 insertions(+), 20 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala index f457ff9dae..828ae7f850 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala @@ -20,9 +20,8 @@ import java.util.UUID import akka.testkit.{TestFSMRef, TestKitBase, TestProbe} import fr.acinq.bitcoin.{ByteVector32, Crypto} -import fr.acinq.eclair.TestConstants.{Alice, Bob, TestFeeEstimator} +import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair.blockchain._ -import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeratesPerKw} import fr.acinq.eclair.channel._ import fr.acinq.eclair.payment.PaymentLifecycle import fr.acinq.eclair.router.Hop @@ -43,11 +42,9 @@ trait StateTestsHelperMethods extends TestKitBase { router: TestProbe, relayerA: TestProbe, relayerB: TestProbe, - channelUpdateListener: TestProbe, - feeEstimator: TestFeeEstimator) + channelUpdateListener: TestProbe) def init(nodeParamsA: NodeParams = TestConstants.Alice.nodeParams, nodeParamsB: NodeParams = TestConstants.Bob.nodeParams, wallet: EclairWallet = new TestWallet): SetupFixture = { - val testFeeEstimator = new TestFeeEstimator val alice2bob = TestProbe() val bob2alice = TestProbe() val alice2blockchain = TestProbe() @@ -58,9 +55,9 @@ trait StateTestsHelperMethods extends TestKitBase { system.eventStream.subscribe(channelUpdateListener.ref, classOf[LocalChannelUpdate]) system.eventStream.subscribe(channelUpdateListener.ref, classOf[LocalChannelDown]) val router = TestProbe() - val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(nodeParamsA.copy(feeEstimator = testFeeEstimator), wallet, Bob.nodeParams.nodeId, alice2blockchain.ref, router.ref, relayerA.ref)) - val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(nodeParamsB.copy(feeEstimator = testFeeEstimator), wallet, Alice.nodeParams.nodeId, bob2blockchain.ref, router.ref, relayerB.ref)) - SetupFixture(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, router, relayerA, relayerB, channelUpdateListener, testFeeEstimator) + val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(nodeParamsA, wallet, Bob.nodeParams.nodeId, alice2blockchain.ref, router.ref, relayerA.ref)) + val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(nodeParamsB, wallet, Alice.nodeParams.nodeId, bob2blockchain.ref, router.ref, relayerB.ref)) + SetupFixture(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, router, relayerA, relayerB, channelUpdateListener) } def reachNormal(setup: SetupFixture, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 909c1298ef..6e115a2c23 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -23,7 +23,7 @@ import akka.actor.Status.Failure import akka.testkit.TestProbe import fr.acinq.bitcoin.Crypto.PrivateKey import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, Satoshi, ScriptFlags, Transaction} -import fr.acinq.eclair.TestConstants.{Alice, Bob} +import fr.acinq.eclair.TestConstants.{Alice, Bob, TestFeeEstimator} import fr.acinq.eclair.UInt64.Conversions._ import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.blockchain.fee.FeeratesPerKw @@ -1393,7 +1393,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val sender = TestProbe() val fee = UpdateFee(ByteVector32.Zeroes, 100000000) // we first update the feerates so that we don't trigger a 'fee too different' error - feeEstimator.setFeerate(FeeratesPerKw.single(fee.feeratePerKw)) + bob.underlyingActor.nodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(fee.feeratePerKw)) sender.send(bob, fee) val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray) === CannotAffordFees(channelId(bob), missingSatoshis = 71620000L, reserveSatoshis = 20000L, feesSatoshis = 72400000L).getMessage) @@ -1407,11 +1407,12 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { test("recv UpdateFee (local/remote feerates are too different)") { f => import f._ - feeEstimator.setFeerate(FeeratesPerKw(1000, 2000, 6000, 12000, 36000, 72000, 140000)) + val bobNodeParams = bob.underlyingActor.nodeParams + bobNodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw(1000, 2000, 6000, 12000, 36000, 72000, 140000)) val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx val sender = TestProbe() // Alice will use $localFeeRate when performing the checks for update_fee - val localFeeRate = feeEstimator.getFeeratePerKw(Alice.nodeParams.feeTargets.commitmentBlockTarget) + val localFeeRate = bobNodeParams.feeEstimator.getFeeratePerKw(bobNodeParams.feeTargets.commitmentBlockTarget) assert(localFeeRate === 2000) val remoteFeeUpdate = 85000 sender.send(bob, UpdateFee(ByteVector32.Zeroes, remoteFeeUpdate)) @@ -1427,9 +1428,11 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { test("recv UpdateFee (remote feerate is too small)") { f => import f._ - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + val bobNodeParams = bob.underlyingActor.nodeParams + val bobCommitments = bob.stateData.asInstanceOf[DATA_NORMAL].commitments + val tx = bobCommitments.localCommit.publishableTxs.commitTx.tx val sender = TestProbe() - assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.feeratePerKw == feeEstimator.getFeeratePerKw(Bob.nodeParams.feeTargets.commitmentBlockTarget)) + assert(bobCommitments.localCommit.spec.feeratePerKw == bobNodeParams.feeEstimator.getFeeratePerKw(bobNodeParams.feeTargets.commitmentBlockTarget)) sender.send(bob, UpdateFee(ByteVector32.Zeroes, 252)) val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray) === "remote fee rate is too small: remoteFeeratePerKw=252") @@ -1893,7 +1896,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get.claimHtlcTimeoutTxs.size == 2) // assert the feerate of the claim main is what we expect - val expectedFeeRate = feeEstimator.getFeeratePerKw(Alice.nodeParams.feeTargets.claimMainBlockTarget) + val aliceNodeParams = alice.underlyingActor.nodeParams + val expectedFeeRate = aliceNodeParams.feeEstimator.getFeeratePerKw(aliceNodeParams.feeTargets.claimMainBlockTarget) val expectedFee = Transactions.weight2fee(expectedFeeRate, Transactions.claimP2WPKHOutputWeight).toLong val claimFee = claimMain.txIn.map(in => bobCommitTx.txOut(in.outPoint.index.toInt).amount.toLong).sum - claimMain.txOut.map(_.amount.toLong).sum assert(claimFee == expectedFee) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala index 1451172bab..9fe2edb667 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala @@ -582,7 +582,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val sender = TestProbe() val fee = UpdateFee(ByteVector32.Zeroes, 100000000) // we first update the feerates so that we don't trigger a 'fee too different' error - feeEstimator.setFeerate(FeeratesPerKw.single(fee.feeratePerKw)) + bob.underlyingActor.nodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(fee.feeratePerKw)) sender.send(bob, fee) val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray) === CannotAffordFees(channelId(bob), missingSatoshis = 72120000L, reserveSatoshis = 20000L, feesSatoshis = 72400000L).getMessage) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala index 869aae7450..f29bf0e19c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala @@ -22,7 +22,7 @@ import akka.actor.Status.Failure import akka.event.LoggingAdapter import akka.testkit.TestProbe import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Satoshi} -import fr.acinq.eclair.TestConstants.{Alice, Bob} +import fr.acinq.eclair.TestConstants.{Alice, Bob, TestFeeEstimator} import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeratesPerKw} import fr.acinq.eclair.channel.Helpers.Closing @@ -52,7 +52,14 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods reachNormal(setup) val sender = TestProbe() // alice initiates a closing - if (test.tags.contains("fee2")) feeEstimator.setFeerate(FeeratesPerKw.single(4319)) else feeEstimator.setFeerate(FeeratesPerKw.single(10000)) + if (test.tags.contains("fee2")) { + alice.underlyingActor.nodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(4319)) + bob.underlyingActor.nodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(4319)) + } + else { + alice.underlyingActor.nodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(10000)) + bob.underlyingActor.nodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(10000)) + } sender.send(bob, CMD_CLOSE(None)) bob2alice.expectMsgType[Shutdown] bob2alice.forward(alice) @@ -61,7 +68,13 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods // NB: at this point, alice has already computed and sent the first ClosingSigned message // In order to force a fee negotiation, we will change the current fee before forwarding // the Shutdown message to alice, so that alice computes a different initial closing fee. - if (test.tags.contains("fee2")) feeEstimator.setFeerate(FeeratesPerKw.single(4316)) else feeEstimator.setFeerate(FeeratesPerKw.single(5000)) + if (test.tags.contains("fee2")) { + alice.underlyingActor.nodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(4316)) + bob.underlyingActor.nodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(4316)) + } else { + alice.underlyingActor.nodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(5000)) + bob.underlyingActor.nodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(5000)) + } alice2bob.forward(bob) awaitCond(bob.stateName == NEGOTIATING) withFixture(test.toNoArgTest(setup)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala index 263d154d40..3d3e10077d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala @@ -332,7 +332,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { bob2alice.forward(alice) // agreeing on a closing fee val aliceCloseFee = alice2bob.expectMsgType[ClosingSigned].feeSatoshis - val nodeParams = alice.underlyingActor.nodeParams + val nodeParams = bob.underlyingActor.nodeParams nodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(100)) alice2bob.forward(bob) val bobCloseFee = bob2alice.expectMsgType[ClosingSigned].feeSatoshis From b652fb681728ca8c50124ca87a71f066ffe5b4ee Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 23 Jul 2019 17:00:36 +0200 Subject: [PATCH 16/25] Group together on-chain fees related configurations --- eclair-core/src/main/resources/reference.conf | 30 +++++------ .../scala/fr/acinq/eclair/NodeParams.scala | 32 +++++++----- .../fr/acinq/eclair/channel/Channel.scala | 50 +++++++++---------- .../fr/acinq/eclair/channel/Helpers.scala | 4 +- .../main/scala/fr/acinq/eclair/io/Peer.scala | 4 +- .../scala/fr/acinq/eclair/TestConstants.scala | 22 ++++---- .../channel/states/e/NormalStateSpec.scala | 12 ++--- .../channel/states/f/ShutdownStateSpec.scala | 4 +- .../states/g/NegotiatingStateSpec.scala | 16 +++--- .../channel/states/h/ClosingStateSpec.scala | 8 +-- .../interop/rustytests/RustyTestsSpec.scala | 4 +- .../scala/fr/acinq/eclair/io/PeerSpec.scala | 4 +- 12 files changed, 101 insertions(+), 89 deletions(-) diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf index 8ca594b410..b08740cdb0 100644 --- a/eclair-core/src/main/resources/reference.conf +++ b/eclair-core/src/main/resources/reference.conf @@ -41,14 +41,6 @@ eclair { } } - // number of blocks to target when computing fees for each transaction type - fee-targets { - funding = 6 // target for the funding transaction - commitment = 2 // target for the commitment transaction (used in force-close scenario) *do not change this unless you know what you are doing* - mutual-close = 12 // target for the mutual close transaction - claim-main = 6 // target for the claim main transaction (tx that spends main channel output back to wallet) - } - min-feerate = 3 // minimum feerate in satoshis per byte smooth-feerate-window = 6 // 1 = no smoothing @@ -87,13 +79,23 @@ eclair { fee-base-msat = 1000 fee-proportional-millionths = 100 // fee charged per transferred satoshi in millionths of a satoshi (100 = 0.01%) - // 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.56 // will allow remote fee rates up to 8x bigger or smaller than our local fee rate + on-chain-fees { + // number of blocks to target when computing fees for each transaction type + fee-targets { + funding = 6 // target for the funding transaction + commitment = 2 // target for the commitment transaction (used in force-close scenario) *do not change this unless you know what you are doing* + mutual-close = 12 // target for the mutual close transaction + claim-main = 6 // target for the claim main transaction (tx that spends main channel output back to wallet) + } - // 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 + // 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.56 // will allow remote fee rates up to 8x bigger or smaller than our local fee rate + + // 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 + } revocation-timeout = 20 seconds // after sending a commit_sig, we will wait for at most that duration before disconnecting diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala index 129afc608b..b0ce08c1de 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala @@ -24,7 +24,7 @@ import java.util.concurrent.TimeUnit import com.typesafe.config.{Config, ConfigFactory} import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.{Block, ByteVector32} -import fr.acinq.eclair.NodeParams.WatcherType +import fr.acinq.eclair.NodeParams.{OnChainFeeConf, WatcherType} import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeTargets} import fr.acinq.eclair.channel.Channel import fr.acinq.eclair.crypto.KeyManager @@ -56,7 +56,6 @@ case class NodeParams(keyManager: KeyManager, toRemoteDelayBlocks: Int, maxToLocalDelayBlocks: Int, minDepthBlocks: Int, - feeEstimator: FeeEstimator, feeBaseMsat: Int, feeProportionalMillionth: Int, reserveToFundingRatio: Double, @@ -66,9 +65,7 @@ case class NodeParams(keyManager: KeyManager, pingInterval: FiniteDuration, pingTimeout: FiniteDuration, pingDisconnect: Boolean, - feeTargets: FeeTargets, - maxFeerateMismatch: Double, - updateFeeMinDiffRatio: Double, + onChainFeeConf: OnChainFeeConf, autoReconnect: Boolean, initialRandomReconnectDelay: FiniteDuration, maxReconnectInterval: FiniteDuration, @@ -93,6 +90,13 @@ object NodeParams { object ELECTRUM extends WatcherType + case class OnChainFeeConf( + feeTargets: FeeTargets, + feeEstimator: FeeEstimator, + maxFeerateMismatch: Double, + updateFeeMinDiffRatio: Double + ) + /** * Order of precedence for the configuration parameters: * 1) Java environment variables (-D...) @@ -184,10 +188,10 @@ object NodeParams { .map(ip => NodeAddress.fromParts(ip, config.getInt("server.port")).get) ++ torAddress_opt val feeTargets = FeeTargets( - fundingBlockTarget = config.getInt("fee-targets.funding"), - commitmentBlockTarget = config.getInt("fee-targets.commitment"), - mutualCloseBlockTarget = config.getInt("fee-targets.mutual-close"), - claimMainBlockTarget = config.getInt("fee-targets.claim-main") + fundingBlockTarget = config.getInt("on-chain-fees.fee-targets.funding"), + commitmentBlockTarget = config.getInt("on-chain-fees.fee-targets.commitment"), + mutualCloseBlockTarget = config.getInt("on-chain-fees.fee-targets.mutual-close"), + claimMainBlockTarget = config.getInt("on-chain-fees.fee-targets.claim-main") ) NodeParams( @@ -207,7 +211,6 @@ object NodeParams { toRemoteDelayBlocks = config.getInt("to-remote-delay-blocks"), maxToLocalDelayBlocks = config.getInt("max-to-local-delay-blocks"), minDepthBlocks = config.getInt("mindepth-blocks"), - feeEstimator = feeEstimator, feeBaseMsat = config.getInt("fee-base-msat"), feeProportionalMillionth = config.getInt("fee-proportional-millionths"), reserveToFundingRatio = config.getDouble("reserve-to-funding-ratio"), @@ -217,9 +220,12 @@ object NodeParams { pingInterval = FiniteDuration(config.getDuration("ping-interval").getSeconds, TimeUnit.SECONDS), pingTimeout = FiniteDuration(config.getDuration("ping-timeout").getSeconds, TimeUnit.SECONDS), pingDisconnect = config.getBoolean("ping-disconnect"), - feeTargets = feeTargets, - maxFeerateMismatch = config.getDouble("max-feerate-mismatch"), - updateFeeMinDiffRatio = config.getDouble("update-fee_min-diff-ratio"), + onChainFeeConf = OnChainFeeConf( + feeTargets = feeTargets, + feeEstimator = feeEstimator, + maxFeerateMismatch = config.getDouble("on-chain-fees.max-feerate-mismatch"), + updateFeeMinDiffRatio = config.getDouble("on-chain-fees.update-fee_min-diff-ratio") + ), autoReconnect = config.getBoolean("auto-reconnect"), initialRandomReconnectDelay = FiniteDuration(config.getDuration("initial-random-reconnect-delay").getSeconds, TimeUnit.SECONDS), maxReconnectInterval = FiniteDuration(config.getDuration("max-reconnect-interval").getSeconds, TimeUnit.SECONDS), diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 5882d5362a..aa1327137e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -362,7 +362,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId when(WAIT_FOR_FUNDING_INTERNAL)(handleExceptions { case Event(MakeFundingTxResponse(fundingTx, fundingTxOutputIndex, fundingTxFee), DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, remoteFirstPerCommitmentPoint, open)) => // let's create the first commitment tx that spends the yet uncommitted funding tx - val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(keyManager, temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTx.hash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint, nodeParams.maxFeerateMismatch) + val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(keyManager, temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTx.hash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint, nodeParams.onChainFeeConf.maxFeerateMismatch) require(fundingTx.txOut(fundingTxOutputIndex).publicKeyScript == localCommitTx.input.txOut.publicKeyScript, s"pubkey script mismatch!") val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) // signature of their initial commitment tx that pays remote pushMsat @@ -403,7 +403,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId when(WAIT_FOR_FUNDING_CREATED)(handleExceptions { case Event(FundingCreated(_, fundingTxHash, fundingTxOutputIndex, remoteSig), d@DATA_WAIT_FOR_FUNDING_CREATED(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, remoteFirstPerCommitmentPoint, channelFlags, _)) => // they fund the channel with their funding tx, so the money is theirs (but we are paid pushMsat) - val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(keyManager, temporaryChannelId, localParams, remoteParams, fundingSatoshis: Long, pushMsat, initialFeeratePerKw, fundingTxHash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint, nodeParams.maxFeerateMismatch) + val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(keyManager, temporaryChannelId, localParams, remoteParams, fundingSatoshis: Long, pushMsat, initialFeeratePerKw, fundingTxHash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint, nodeParams.onChainFeeConf.maxFeerateMismatch) // check remote signature validity val localSigOfLocalTx = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) @@ -681,7 +681,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId } case Event(fee: UpdateFee, d: DATA_NORMAL) => - Try(Commitments.receiveFee(d.commitments, nodeParams.feeEstimator, nodeParams.feeTargets, fee, nodeParams.maxFeerateMismatch)) match { + Try(Commitments.receiveFee(d.commitments, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets, fee, nodeParams.onChainFeeConf.maxFeerateMismatch)) match { case Success(commitments1) => stay using d.copy(commitments = commitments1) case Failure(cause) => handleLocalError(cause, d, Some(fee)) } @@ -836,7 +836,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId // there are no pending signed htlcs, let's go directly to NEGOTIATING if (d.commitments.localParams.isFunder) { // we are funder, need to initiate the negotiation by sending the first closing_signed - val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, d.commitments, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey, nodeParams.feeEstimator, nodeParams.feeTargets) + val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, d.commitments, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets) goto(NEGOTIATING) using store(DATA_NEGOTIATING(d.commitments, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending sendList :+ closingSigned } else { // we are fundee, will wait for their closing_signed @@ -852,12 +852,12 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId case Event(c: CurrentBlockCount, d: DATA_NORMAL) => handleNewBlock(c, d) case Event(c@CurrentFeerates(feeratesPerKw), d: DATA_NORMAL) => - val networkFeeratePerKw = feeratesPerKw.feePerBlock(target = nodeParams.feeTargets.commitmentBlockTarget) + val networkFeeratePerKw = feeratesPerKw.feePerBlock(target = nodeParams.onChainFeeConf.feeTargets.commitmentBlockTarget) d.commitments.localParams.isFunder match { - case true if Helpers.shouldUpdateFee(d.commitments.localCommit.spec.feeratePerKw, networkFeeratePerKw, nodeParams.updateFeeMinDiffRatio) => + case true if Helpers.shouldUpdateFee(d.commitments.localCommit.spec.feeratePerKw, networkFeeratePerKw, nodeParams.onChainFeeConf.updateFeeMinDiffRatio) => self ! CMD_UPDATE_FEE(networkFeeratePerKw, commit = true) stay - case false if Helpers.isFeeDiffTooHigh(d.commitments.localCommit.spec.feeratePerKw, networkFeeratePerKw, nodeParams.maxFeerateMismatch) => + case false if Helpers.isFeeDiffTooHigh(d.commitments.localCommit.spec.feeratePerKw, networkFeeratePerKw, nodeParams.onChainFeeConf.maxFeerateMismatch) => handleLocalError(FeerateTooDifferent(d.channelId, localFeeratePerKw = networkFeeratePerKw, remoteFeeratePerKw = d.commitments.localCommit.spec.feeratePerKw), d, Some(c)) case _ => stay } @@ -1039,7 +1039,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId } case Event(fee: UpdateFee, d: DATA_SHUTDOWN) => - Try(Commitments.receiveFee(d.commitments, nodeParams.feeEstimator, nodeParams.feeTargets, fee, nodeParams.maxFeerateMismatch)) match { + Try(Commitments.receiveFee(d.commitments, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets, fee, nodeParams.onChainFeeConf.maxFeerateMismatch)) match { case Success(commitments1) => stay using d.copy(commitments = commitments1) case Failure(cause) => handleLocalError(cause, d, Some(fee)) } @@ -1078,7 +1078,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId if (commitments1.hasNoPendingHtlcs) { if (d.commitments.localParams.isFunder) { // we are funder, need to initiate the negotiation by sending the first closing_signed - val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey, nodeParams.feeEstimator, nodeParams.feeTargets) + val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets) goto(NEGOTIATING) using store(DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending revocation :: closingSigned :: Nil } else { // we are fundee, will wait for their closing_signed @@ -1114,7 +1114,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId log.debug(s"switching to NEGOTIATING spec:\n${Commitments.specs2String(commitments1)}") if (d.commitments.localParams.isFunder) { // we are funder, need to initiate the negotiation by sending the first closing_signed - val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey, nodeParams.feeEstimator, nodeParams.feeTargets) + val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets) goto(NEGOTIATING) using store(DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending closingSigned } else { // we are fundee, will wait for their closing_signed @@ -1134,12 +1134,12 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId case Event(c: CurrentBlockCount, d: DATA_SHUTDOWN) => handleNewBlock(c, d) case Event(c@CurrentFeerates(feeratesPerKw), d: DATA_SHUTDOWN) => - val networkFeeratePerKw = feeratesPerKw.feePerBlock(target = nodeParams.feeTargets.commitmentBlockTarget) + val networkFeeratePerKw = feeratesPerKw.feePerBlock(target = nodeParams.onChainFeeConf.feeTargets.commitmentBlockTarget) d.commitments.localParams.isFunder match { - case true if Helpers.shouldUpdateFee(d.commitments.localCommit.spec.feeratePerKw, networkFeeratePerKw, nodeParams.updateFeeMinDiffRatio) => + case true if Helpers.shouldUpdateFee(d.commitments.localCommit.spec.feeratePerKw, networkFeeratePerKw, nodeParams.onChainFeeConf.updateFeeMinDiffRatio) => self ! CMD_UPDATE_FEE(networkFeeratePerKw, commit = true) stay - case false if Helpers.isFeeDiffTooHigh(d.commitments.localCommit.spec.feeratePerKw, networkFeeratePerKw, nodeParams.maxFeerateMismatch) => + case false if Helpers.isFeeDiffTooHigh(d.commitments.localCommit.spec.feeratePerKw, networkFeeratePerKw, nodeParams.onChainFeeConf.maxFeerateMismatch) => handleLocalError(FeerateTooDifferent(d.channelId, localFeeratePerKw = networkFeeratePerKw, remoteFeeratePerKw = d.commitments.localCommit.spec.feeratePerKw), d, Some(c)) case _ => stay } @@ -1167,7 +1167,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId // if we are fundee and we were waiting for them to send their first closing_signed, we don't have a lastLocalClosingFee, so we compute a firstClosingFee val lastLocalClosingFee = d.closingTxProposed.last.lastOption.map(_.localClosingSigned.feeSatoshis).map(Satoshi) val nextClosingFee = Closing.nextClosingFee( - localClosingFee = lastLocalClosingFee.getOrElse(Closing.firstClosingFee(d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.feeEstimator, nodeParams.feeTargets)), + localClosingFee = lastLocalClosingFee.getOrElse(Closing.firstClosingFee(d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets)), remoteClosingFee = Satoshi(remoteClosingFee)) val (closingTx, closingSigned) = Closing.makeClosingTx(keyManager, d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nextClosingFee) if (lastLocalClosingFee.contains(nextClosingFee)) { @@ -1212,19 +1212,19 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId log.info(s"got valid payment preimage, recalculating transactions to redeem the corresponding htlc on-chain") val localCommitPublished1 = d.localCommitPublished.map { localCommitPublished => - val localCommitPublished1 = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, commitments1, localCommitPublished.commitTx, nodeParams.feeEstimator, nodeParams.feeTargets) + val localCommitPublished1 = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, commitments1, localCommitPublished.commitTx, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets) doPublish(localCommitPublished1) localCommitPublished1 } val remoteCommitPublished1 = d.remoteCommitPublished.map { remoteCommitPublished => - val remoteCommitPublished1 = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, commitments1, commitments1.remoteCommit, remoteCommitPublished.commitTx, nodeParams.feeEstimator, nodeParams.feeTargets) + val remoteCommitPublished1 = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, commitments1, commitments1.remoteCommit, remoteCommitPublished.commitTx, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets) doPublish(remoteCommitPublished1) remoteCommitPublished1 } val nextRemoteCommitPublished1 = d.nextRemoteCommitPublished.map { remoteCommitPublished => - val remoteCommitPublished1 = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, commitments1, commitments1.remoteCommit, remoteCommitPublished.commitTx, nodeParams.feeEstimator, nodeParams.feeTargets) + val remoteCommitPublished1 = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, commitments1, commitments1.remoteCommit, remoteCommitPublished.commitTx, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets) doPublish(remoteCommitPublished1) remoteCommitPublished1 } @@ -1289,7 +1289,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId } } val revokedCommitPublished1 = d.revokedCommitPublished.map { rev => - val (rev1, tx_opt) = Closing.claimRevokedHtlcTxOutputs(keyManager, d.commitments, rev, tx, nodeParams.feeEstimator) + val (rev1, tx_opt) = Closing.claimRevokedHtlcTxOutputs(keyManager, d.commitments, rev, tx, nodeParams.onChainFeeConf.feeEstimator) tx_opt.foreach(claimTx => blockchain ! PublishAsap(claimTx)) tx_opt.foreach(claimTx => blockchain ! WatchSpent(self, tx, claimTx.txIn.head.outPoint.index.toInt, BITCOIN_OUTPUT_SPENT)) rev1 @@ -1548,7 +1548,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId // note: in any case we still need to keep all previously sent closing_signed, because they may publish one of them if (d.commitments.localParams.isFunder) { // we could use the last closing_signed we sent, but network fees may have changed while we were offline so it is better to restart from scratch - val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.feeEstimator, nodeParams.feeTargets) + val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets) val closingTxProposed1 = d.closingTxProposed :+ List(ClosingTxProposed(closingTx.tx, closingSigned)) goto(NEGOTIATING) using store(d.copy(closingTxProposed = closingTxProposed1)) sending d.localShutdown :: closingSigned :: Nil } else { @@ -1928,7 +1928,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId } else { val commitTx = d.commitments.localCommit.publishableTxs.commitTx.tx - val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, d.commitments, commitTx, nodeParams.feeEstimator, nodeParams.feeTargets) + val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, d.commitments, commitTx, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets) doPublish(localCommitPublished) val nextData = d match { @@ -1993,7 +1993,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId log.warning(s"they published their current commit in txid=${commitTx.txid}") require(commitTx.txid == d.commitments.remoteCommit.txid, "txid mismatch") - val remoteCommitPublished = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, d.commitments, d.commitments.remoteCommit, commitTx, nodeParams.feeEstimator, nodeParams.feeTargets) + val remoteCommitPublished = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, d.commitments, d.commitments.remoteCommit, commitTx, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets) doPublish(remoteCommitPublished) val nextData = d match { @@ -2010,7 +2010,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId log.warning(s"they published their future commit (because we asked them to) in txid=${commitTx.txid}") // if we are in this state, then this field is defined val remotePerCommitmentPoint = d.remoteChannelReestablish.myCurrentPerCommitmentPoint.get - val remoteCommitPublished = Helpers.Closing.claimRemoteCommitMainOutput(keyManager, d.commitments, remotePerCommitmentPoint, commitTx, nodeParams.feeEstimator, nodeParams.feeTargets) + val remoteCommitPublished = Helpers.Closing.claimRemoteCommitMainOutput(keyManager, d.commitments, remotePerCommitmentPoint, commitTx, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets) val nextData = DATA_CLOSING(d.commitments, fundingTx = None, waitingSince = now, Nil, futureRemoteCommitPublished = Some(remoteCommitPublished)) doPublish(remoteCommitPublished) @@ -2023,7 +2023,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId val remoteCommit = d.commitments.remoteNextCommitInfo.left.get.nextRemoteCommit require(commitTx.txid == remoteCommit.txid, "txid mismatch") - val remoteCommitPublished = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, d.commitments, remoteCommit, commitTx, nodeParams.feeEstimator, nodeParams.feeTargets) + val remoteCommitPublished = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, d.commitments, remoteCommit, commitTx, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets) doPublish(remoteCommitPublished) val nextData = d match { @@ -2056,7 +2056,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId def handleRemoteSpentOther(tx: Transaction, d: HasCommitments) = { log.warning(s"funding tx spent in txid=${tx.txid}") - Helpers.Closing.claimRevokedRemoteCommitTxOutputs(keyManager, d.commitments, tx, nodeParams.db.channels, nodeParams.feeEstimator, nodeParams.feeTargets) match { + Helpers.Closing.claimRevokedRemoteCommitTxOutputs(keyManager, d.commitments, tx, nodeParams.db.channels, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets) match { case Some(revokedCommitPublished) => log.warning(s"txid=${tx.txid} was a revoked commitment, publishing the penalty tx") val exc = FundingTxSpent(d.channelId, tx) @@ -2103,7 +2103,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId // let's try to spend our current local tx val commitTx = d.commitments.localCommit.publishableTxs.commitTx.tx - val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, d.commitments, commitTx, nodeParams.feeEstimator, nodeParams.feeTargets) + val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, d.commitments, commitTx, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets) doPublish(localCommitPublished) goto(ERR_INFORMATION_LEAK) sending error diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 8377f88876..ca9df1b2d5 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -112,8 +112,8 @@ object Helpers { throw ChannelReserveNotMet(open.temporaryChannelId, toLocalMsat, toRemoteMsat, open.channelReserveSatoshis) } - val localFeeratePerKw = nodeParams.feeEstimator.getFeeratePerKw(target = nodeParams.feeTargets.commitmentBlockTarget) - if (isFeeDiffTooHigh(open.feeratePerKw, localFeeratePerKw, nodeParams.maxFeerateMismatch)) throw FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.feeratePerKw) + val localFeeratePerKw = nodeParams.onChainFeeConf.feeEstimator.getFeeratePerKw(target = nodeParams.onChainFeeConf.feeTargets.commitmentBlockTarget) + if (isFeeDiffTooHigh(open.feeratePerKw, localFeeratePerKw, nodeParams.onChainFeeConf.maxFeerateMismatch)) throw FeerateTooDifferent(open.temporaryChannelId, localFeeratePerKw, open.feeratePerKw) // only enforce dust limit check on mainnet if (nodeParams.chainHash == Block.LivenetGenesisBlock.hash) { if (open.dustLimitSatoshis < Channel.MIN_DUSTLIMIT) throw DustLimitTooSmall(open.temporaryChannelId, open.dustLimitSatoshis, Channel.MIN_DUSTLIMIT) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index 1c9b573a76..dbc91f208e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -283,8 +283,8 @@ class Peer(val nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: A val (channel, localParams) = createNewChannel(nodeParams, funder = true, c.fundingSatoshis.toLong, origin_opt = Some(sender)) c.timeout_opt.map(openTimeout => context.system.scheduler.scheduleOnce(openTimeout.duration, channel, Channel.TickChannelOpenTimeout)(context.dispatcher)) val temporaryChannelId = randomBytes32 - val channelFeeratePerKw = nodeParams.feeEstimator.getFeeratePerKw(target = nodeParams.feeTargets.commitmentBlockTarget) - val fundingTxFeeratePerKw = c.fundingTxFeeratePerKw_opt.getOrElse(nodeParams.feeEstimator.getFeeratePerKw(target = nodeParams.feeTargets.fundingBlockTarget)) + val channelFeeratePerKw = nodeParams.onChainFeeConf.feeEstimator.getFeeratePerKw(target = nodeParams.onChainFeeConf.feeTargets.commitmentBlockTarget) + val fundingTxFeeratePerKw = c.fundingTxFeeratePerKw_opt.getOrElse(nodeParams.onChainFeeConf.feeEstimator.getFeeratePerKw(target = nodeParams.onChainFeeConf.feeTargets.fundingBlockTarget)) log.info(s"requesting a new channel with fundingSatoshis=${c.fundingSatoshis}, pushMsat=${c.pushMsat} and fundingFeeratePerByte=${c.fundingTxFeeratePerKw_opt} temporaryChannelId=$temporaryChannelId localParams=$localParams") channel ! INPUT_INIT_FUNDER(temporaryChannelId, c.fundingSatoshis.amount, c.pushMsat.amount, channelFeeratePerKw, fundingTxFeeratePerKw, localParams, d.transport, d.remoteInit, c.channelFlags.getOrElse(nodeParams.channelFlags)) stay using d.copy(channels = d.channels + (TemporaryChannelId(temporaryChannelId) -> channel)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala index bc6a7ac145..9224bef42d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala @@ -20,7 +20,7 @@ import java.sql.{Connection, DriverManager} import fr.acinq.bitcoin.Crypto.PrivateKey import fr.acinq.bitcoin.{Block, ByteVector32, Script} -import fr.acinq.eclair.NodeParams.BITCOIND +import fr.acinq.eclair.NodeParams.{BITCOIND, OnChainFeeConf} import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeTargets, FeeratesPerKw} import fr.acinq.eclair.crypto.LocalKeyManager import fr.acinq.eclair.db._ @@ -79,7 +79,6 @@ object TestConstants { minDepthBlocks = 3, toRemoteDelayBlocks = 144, maxToLocalDelayBlocks = 1000, - feeEstimator = new TestFeeEstimator, feeBaseMsat = 546000, feeProportionalMillionth = 10, reserveToFundingRatio = 0.01, // note: not used (overridden below) @@ -89,9 +88,12 @@ object TestConstants { pingInterval = 30 seconds, pingTimeout = 10 seconds, pingDisconnect = true, - feeTargets = FeeTargets(6, 2, 2, 6), - maxFeerateMismatch = 1.5, - updateFeeMinDiffRatio = 0.1, + onChainFeeConf = OnChainFeeConf( + feeTargets = FeeTargets(6, 2, 2, 6), + feeEstimator = new TestFeeEstimator, + maxFeerateMismatch = 1.5, + updateFeeMinDiffRatio = 0.1 + ), autoReconnect = false, initialRandomReconnectDelay = 5 seconds, maxReconnectInterval = 1 hour, @@ -147,7 +149,6 @@ object TestConstants { minDepthBlocks = 3, toRemoteDelayBlocks = 144, maxToLocalDelayBlocks = 1000, - feeEstimator = new TestFeeEstimator, feeBaseMsat = 546000, feeProportionalMillionth = 10, reserveToFundingRatio = 0.01, // note: not used (overridden below) @@ -157,9 +158,12 @@ object TestConstants { pingInterval = 30 seconds, pingTimeout = 10 seconds, pingDisconnect = true, - feeTargets = FeeTargets(6, 2, 2, 6), - maxFeerateMismatch = 1.0, - updateFeeMinDiffRatio = 0.1, + onChainFeeConf = OnChainFeeConf( + feeTargets = FeeTargets(6, 2, 2, 6), + feeEstimator = new TestFeeEstimator, + maxFeerateMismatch = 1.0, + updateFeeMinDiffRatio = 0.1 + ), autoReconnect = false, initialRandomReconnectDelay = 5 seconds, maxReconnectInterval = 1 hour, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 6e115a2c23..5c24664fc8 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -1393,7 +1393,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val sender = TestProbe() val fee = UpdateFee(ByteVector32.Zeroes, 100000000) // we first update the feerates so that we don't trigger a 'fee too different' error - bob.underlyingActor.nodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(fee.feeratePerKw)) + bob.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(fee.feeratePerKw)) sender.send(bob, fee) val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray) === CannotAffordFees(channelId(bob), missingSatoshis = 71620000L, reserveSatoshis = 20000L, feesSatoshis = 72400000L).getMessage) @@ -1408,11 +1408,11 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { test("recv UpdateFee (local/remote feerates are too different)") { f => import f._ val bobNodeParams = bob.underlyingActor.nodeParams - bobNodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw(1000, 2000, 6000, 12000, 36000, 72000, 140000)) + bobNodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw(1000, 2000, 6000, 12000, 36000, 72000, 140000)) val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx val sender = TestProbe() // Alice will use $localFeeRate when performing the checks for update_fee - val localFeeRate = bobNodeParams.feeEstimator.getFeeratePerKw(bobNodeParams.feeTargets.commitmentBlockTarget) + val localFeeRate = bobNodeParams.onChainFeeConf.feeEstimator.getFeeratePerKw(bobNodeParams.onChainFeeConf.feeTargets.commitmentBlockTarget) assert(localFeeRate === 2000) val remoteFeeUpdate = 85000 sender.send(bob, UpdateFee(ByteVector32.Zeroes, remoteFeeUpdate)) @@ -1432,7 +1432,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val bobCommitments = bob.stateData.asInstanceOf[DATA_NORMAL].commitments val tx = bobCommitments.localCommit.publishableTxs.commitTx.tx val sender = TestProbe() - assert(bobCommitments.localCommit.spec.feeratePerKw == bobNodeParams.feeEstimator.getFeeratePerKw(bobNodeParams.feeTargets.commitmentBlockTarget)) + assert(bobCommitments.localCommit.spec.feeratePerKw == bobNodeParams.onChainFeeConf.feeEstimator.getFeeratePerKw(bobNodeParams.onChainFeeConf.feeTargets.commitmentBlockTarget)) sender.send(bob, UpdateFee(ByteVector32.Zeroes, 252)) val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray) === "remote fee rate is too small: remoteFeeratePerKw=252") @@ -1810,7 +1810,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] val event = CurrentFeerates(FeeratesPerKw(100, 200, 600, 1200, 3600, 7200, 14400)) sender.send(alice, event) - alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, event.feeratesPerKw.feePerBlock(Alice.nodeParams.feeTargets.commitmentBlockTarget))) + alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, event.feeratesPerKw.feePerBlock(Alice.nodeParams.onChainFeeConf.feeTargets.commitmentBlockTarget))) } test("recv CurrentFeerate (when funder, doesn't trigger an UpdateFee)") { f => @@ -1897,7 +1897,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { // assert the feerate of the claim main is what we expect val aliceNodeParams = alice.underlyingActor.nodeParams - val expectedFeeRate = aliceNodeParams.feeEstimator.getFeeratePerKw(aliceNodeParams.feeTargets.claimMainBlockTarget) + val expectedFeeRate = aliceNodeParams.onChainFeeConf.feeEstimator.getFeeratePerKw(aliceNodeParams.onChainFeeConf.feeTargets.claimMainBlockTarget) val expectedFee = Transactions.weight2fee(expectedFeeRate, Transactions.claimP2WPKHOutputWeight).toLong val claimFee = claimMain.txIn.map(in => bobCommitTx.txOut(in.outPoint.index.toInt).amount.toLong).sum - claimMain.txOut.map(_.amount.toLong).sum assert(claimFee == expectedFee) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala index 9fe2edb667..945a918a5e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala @@ -582,7 +582,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val sender = TestProbe() val fee = UpdateFee(ByteVector32.Zeroes, 100000000) // we first update the feerates so that we don't trigger a 'fee too different' error - bob.underlyingActor.nodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(fee.feeratePerKw)) + bob.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(fee.feeratePerKw)) sender.send(bob, fee) val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray) === CannotAffordFees(channelId(bob), missingSatoshis = 72120000L, reserveSatoshis = 20000L, feesSatoshis = 72400000L).getMessage) @@ -648,7 +648,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] val event = CurrentFeerates(FeeratesPerKw(100, 200, 600, 1200, 3600, 7200, 14400)) sender.send(alice, event) - alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, event.feeratesPerKw.feePerBlock(Alice.nodeParams.feeTargets.commitmentBlockTarget))) + alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, event.feeratesPerKw.feePerBlock(Alice.nodeParams.onChainFeeConf.feeTargets.commitmentBlockTarget))) } test("recv CurrentFeerate (when funder, doesn't trigger an UpdateFee)") { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala index f29bf0e19c..e3605b1ab8 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala @@ -53,12 +53,12 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods val sender = TestProbe() // alice initiates a closing if (test.tags.contains("fee2")) { - alice.underlyingActor.nodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(4319)) - bob.underlyingActor.nodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(4319)) + alice.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(4319)) + bob.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(4319)) } else { - alice.underlyingActor.nodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(10000)) - bob.underlyingActor.nodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(10000)) + alice.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(10000)) + bob.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(10000)) } sender.send(bob, CMD_CLOSE(None)) bob2alice.expectMsgType[Shutdown] @@ -69,11 +69,11 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods // In order to force a fee negotiation, we will change the current fee before forwarding // the Shutdown message to alice, so that alice computes a different initial closing fee. if (test.tags.contains("fee2")) { - alice.underlyingActor.nodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(4316)) - bob.underlyingActor.nodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(4316)) + alice.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(4316)) + bob.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(4316)) } else { - alice.underlyingActor.nodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(5000)) - bob.underlyingActor.nodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(5000)) + alice.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(5000)) + bob.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(5000)) } alice2bob.forward(bob) awaitCond(bob.stateName == NEGOTIATING) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala index 3d3e10077d..789fc61d5e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala @@ -144,7 +144,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { import f._ val nodeParams = alice.underlyingActor.nodeParams - nodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw(100, 250, 350, 450, 600, 800, 900)) + nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw(100, 250, 350, 450, 600, 800, 900)) val sender = TestProbe() // alice initiates a closing @@ -155,9 +155,9 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { bob2alice.forward(alice) val closing = alice2bob.expectMsgType[ClosingSigned] val aliceData = alice.stateData.asInstanceOf[DATA_NEGOTIATING] - val mutualClosingFeeRate = nodeParams.feeEstimator.getFeeratePerKw(nodeParams.feeTargets.mutualCloseBlockTarget) + val mutualClosingFeeRate = nodeParams.onChainFeeConf.feeEstimator.getFeeratePerKw(nodeParams.onChainFeeConf.feeTargets.mutualCloseBlockTarget) val expectedFirstProposedFee = Closing.firstClosingFee(aliceData.commitments, aliceData.localShutdown.scriptPubKey, aliceData.remoteShutdown.scriptPubKey, mutualClosingFeeRate)(akka.event.NoLogging) - assert(Alice.nodeParams.feeTargets.mutualCloseBlockTarget == 2 && mutualClosingFeeRate == 250) + assert(Alice.nodeParams.onChainFeeConf.feeTargets.mutualCloseBlockTarget == 2 && mutualClosingFeeRate == 250) assert(closing.feeSatoshis == expectedFirstProposedFee.amount) } @@ -333,7 +333,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { // agreeing on a closing fee val aliceCloseFee = alice2bob.expectMsgType[ClosingSigned].feeSatoshis val nodeParams = bob.underlyingActor.nodeParams - nodeParams.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(100)) + nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(100)) alice2bob.forward(bob) val bobCloseFee = bob2alice.expectMsgType[ClosingSigned].feeSatoshis bob2alice.forward(alice) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/RustyTestsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/RustyTestsSpec.scala index 55b6f89b5a..6697116750 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/RustyTestsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/RustyTestsSpec.scala @@ -54,8 +54,8 @@ class RustyTestsSpec extends TestKit(ActorSystem("test")) with Matchers with fix val router = TestProbe() val wallet = new TestWallet val feeEstimator = new TestFeeEstimator - val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(Alice.nodeParams.copy(feeEstimator = feeEstimator), wallet, Bob.nodeParams.nodeId, alice2blockchain.ref, router.ref, relayer)) - val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(Bob.nodeParams.copy(feeEstimator = feeEstimator), wallet, Alice.nodeParams.nodeId, bob2blockchain.ref, router.ref, relayer)) + val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(Alice.nodeParams.copy(onChainFeeConf = Alice.nodeParams.onChainFeeConf.copy(feeEstimator = feeEstimator)), wallet, Bob.nodeParams.nodeId, alice2blockchain.ref, router.ref, relayer)) + val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(Bob.nodeParams.copy(onChainFeeConf = Bob.nodeParams.onChainFeeConf.copy(feeEstimator = feeEstimator)), wallet, Alice.nodeParams.nodeId, bob2blockchain.ref, router.ref, relayer)) val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures) val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures) // alice and bob will both have 1 000 000 sat diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala index 41da828386..27f1dd1058 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala @@ -251,8 +251,8 @@ class PeerSpec extends TestkitBaseClass { val nodeParams = peer.underlyingActor.nodeParams val channelCreated = probe.expectMsgType[ChannelCreated] - assert(channelCreated.initialFeeratePerKw == nodeParams.feeEstimator.getFeeratePerKw(nodeParams.feeTargets.commitmentBlockTarget)) - assert(channelCreated.fundingTxFeeratePerKw.get == nodeParams.feeEstimator.getFeeratePerKw(nodeParams.feeTargets.fundingBlockTarget)) + assert(channelCreated.initialFeeratePerKw == nodeParams.onChainFeeConf.feeEstimator.getFeeratePerKw(nodeParams.onChainFeeConf.feeTargets.commitmentBlockTarget)) + assert(channelCreated.fundingTxFeeratePerKw.get == nodeParams.onChainFeeConf.feeEstimator.getFeeratePerKw(nodeParams.onChainFeeConf.feeTargets.fundingBlockTarget)) } test("reply to ping") { f => From 4cf63597ac44effcdbae565224c4da07c02db3f5 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 23 Jul 2019 17:36:34 +0200 Subject: [PATCH 17/25] Use helper functions to get 'OnChainFeeConf' during test --- .../channel/states/StateTestsHelperMethods.scala | 4 ++++ .../channel/states/e/NormalStateSpec.scala | 14 ++++++-------- .../channel/states/f/ShutdownStateSpec.scala | 4 ++-- .../channel/states/g/NegotiatingStateSpec.scala | 16 ++++++++-------- .../channel/states/h/ClosingStateSpec.scala | 10 ++++------ .../test/scala/fr/acinq/eclair/io/PeerSpec.scala | 8 ++++---- 6 files changed, 28 insertions(+), 28 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala index 828ae7f850..26110ed8fd 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala @@ -23,6 +23,7 @@ import fr.acinq.bitcoin.{ByteVector32, Crypto} import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.channel._ +import fr.acinq.eclair.io.Peer import fr.acinq.eclair.payment.PaymentLifecycle import fr.acinq.eclair.router.Hop import fr.acinq.eclair.wire._ @@ -161,4 +162,7 @@ trait StateTestsHelperMethods extends TestKitBase { def channelId(a: TestFSMRef[State, Data, Channel]) = Helpers.getChannelId(a.stateData) + def feeConfOfChannel(a: TestFSMRef[State, Data, Channel]) = a.underlyingActor.nodeParams.onChainFeeConf + def feeConfOfPeer(a: TestFSMRef[Peer.State, Peer.Data, Peer]) = a.underlyingActor.nodeParams.onChainFeeConf + } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 5c24664fc8..872c9bad5f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -1393,7 +1393,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val sender = TestProbe() val fee = UpdateFee(ByteVector32.Zeroes, 100000000) // we first update the feerates so that we don't trigger a 'fee too different' error - bob.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(fee.feeratePerKw)) + feeConfOfChannel(bob).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(fee.feeratePerKw)) sender.send(bob, fee) val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray) === CannotAffordFees(channelId(bob), missingSatoshis = 71620000L, reserveSatoshis = 20000L, feesSatoshis = 72400000L).getMessage) @@ -1407,12 +1407,11 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { test("recv UpdateFee (local/remote feerates are too different)") { f => import f._ - val bobNodeParams = bob.underlyingActor.nodeParams - bobNodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw(1000, 2000, 6000, 12000, 36000, 72000, 140000)) + feeConfOfChannel(bob).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw(1000, 2000, 6000, 12000, 36000, 72000, 140000)) val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx val sender = TestProbe() // Alice will use $localFeeRate when performing the checks for update_fee - val localFeeRate = bobNodeParams.onChainFeeConf.feeEstimator.getFeeratePerKw(bobNodeParams.onChainFeeConf.feeTargets.commitmentBlockTarget) + val localFeeRate = feeConfOfChannel(bob).feeEstimator.getFeeratePerKw(feeConfOfChannel(bob).feeTargets.commitmentBlockTarget) assert(localFeeRate === 2000) val remoteFeeUpdate = 85000 sender.send(bob, UpdateFee(ByteVector32.Zeroes, remoteFeeUpdate)) @@ -1428,11 +1427,11 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { test("recv UpdateFee (remote feerate is too small)") { f => import f._ - val bobNodeParams = bob.underlyingActor.nodeParams val bobCommitments = bob.stateData.asInstanceOf[DATA_NORMAL].commitments val tx = bobCommitments.localCommit.publishableTxs.commitTx.tx val sender = TestProbe() - assert(bobCommitments.localCommit.spec.feeratePerKw == bobNodeParams.onChainFeeConf.feeEstimator.getFeeratePerKw(bobNodeParams.onChainFeeConf.feeTargets.commitmentBlockTarget)) + val expectedFeeratePerKw = feeConfOfChannel(bob).feeEstimator.getFeeratePerKw(feeConfOfChannel(bob).feeTargets.commitmentBlockTarget) + assert(bobCommitments.localCommit.spec.feeratePerKw == expectedFeeratePerKw) sender.send(bob, UpdateFee(ByteVector32.Zeroes, 252)) val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray) === "remote fee rate is too small: remoteFeeratePerKw=252") @@ -1896,8 +1895,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get.claimHtlcTimeoutTxs.size == 2) // assert the feerate of the claim main is what we expect - val aliceNodeParams = alice.underlyingActor.nodeParams - val expectedFeeRate = aliceNodeParams.onChainFeeConf.feeEstimator.getFeeratePerKw(aliceNodeParams.onChainFeeConf.feeTargets.claimMainBlockTarget) + val expectedFeeRate = feeConfOfChannel(alice).feeEstimator.getFeeratePerKw(feeConfOfChannel(alice).feeTargets.claimMainBlockTarget) val expectedFee = Transactions.weight2fee(expectedFeeRate, Transactions.claimP2WPKHOutputWeight).toLong val claimFee = claimMain.txIn.map(in => bobCommitTx.txOut(in.outPoint.index.toInt).amount.toLong).sum - claimMain.txOut.map(_.amount.toLong).sum assert(claimFee == expectedFee) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala index 945a918a5e..d21ed49ce2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala @@ -582,7 +582,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val sender = TestProbe() val fee = UpdateFee(ByteVector32.Zeroes, 100000000) // we first update the feerates so that we don't trigger a 'fee too different' error - bob.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(fee.feeratePerKw)) + feeConfOfChannel(bob).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(fee.feeratePerKw)) sender.send(bob, fee) val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray) === CannotAffordFees(channelId(bob), missingSatoshis = 72120000L, reserveSatoshis = 20000L, feesSatoshis = 72400000L).getMessage) @@ -648,7 +648,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] val event = CurrentFeerates(FeeratesPerKw(100, 200, 600, 1200, 3600, 7200, 14400)) sender.send(alice, event) - alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, event.feeratesPerKw.feePerBlock(Alice.nodeParams.onChainFeeConf.feeTargets.commitmentBlockTarget))) + alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, event.feeratesPerKw.feePerBlock(feeConfOfChannel(alice).feeTargets.commitmentBlockTarget))) } test("recv CurrentFeerate (when funder, doesn't trigger an UpdateFee)") { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala index e3605b1ab8..0ff9603aa5 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala @@ -53,12 +53,12 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods val sender = TestProbe() // alice initiates a closing if (test.tags.contains("fee2")) { - alice.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(4319)) - bob.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(4319)) + feeConfOfChannel(alice).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(4319)) + feeConfOfChannel(bob).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(4319)) } else { - alice.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(10000)) - bob.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(10000)) + feeConfOfChannel(alice).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(10000)) + feeConfOfChannel(bob).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(10000)) } sender.send(bob, CMD_CLOSE(None)) bob2alice.expectMsgType[Shutdown] @@ -69,11 +69,11 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods // In order to force a fee negotiation, we will change the current fee before forwarding // the Shutdown message to alice, so that alice computes a different initial closing fee. if (test.tags.contains("fee2")) { - alice.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(4316)) - bob.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(4316)) + feeConfOfChannel(alice).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(4316)) + feeConfOfChannel(bob).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(4316)) } else { - alice.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(5000)) - bob.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(5000)) + feeConfOfChannel(alice).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(5000)) + feeConfOfChannel(bob).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(5000)) } alice2bob.forward(bob) awaitCond(bob.stateName == NEGOTIATING) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala index 789fc61d5e..0abaf08862 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala @@ -143,8 +143,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { test("start fee negotiation from configured block target") { f => import f._ - val nodeParams = alice.underlyingActor.nodeParams - nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw(100, 250, 350, 450, 600, 800, 900)) + feeConfOfChannel(alice).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw(100, 250, 350, 450, 600, 800, 900)) val sender = TestProbe() // alice initiates a closing @@ -155,9 +154,9 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { bob2alice.forward(alice) val closing = alice2bob.expectMsgType[ClosingSigned] val aliceData = alice.stateData.asInstanceOf[DATA_NEGOTIATING] - val mutualClosingFeeRate = nodeParams.onChainFeeConf.feeEstimator.getFeeratePerKw(nodeParams.onChainFeeConf.feeTargets.mutualCloseBlockTarget) + val mutualClosingFeeRate = feeConfOfChannel(alice).feeEstimator.getFeeratePerKw(feeConfOfChannel(alice).feeTargets.mutualCloseBlockTarget) val expectedFirstProposedFee = Closing.firstClosingFee(aliceData.commitments, aliceData.localShutdown.scriptPubKey, aliceData.remoteShutdown.scriptPubKey, mutualClosingFeeRate)(akka.event.NoLogging) - assert(Alice.nodeParams.onChainFeeConf.feeTargets.mutualCloseBlockTarget == 2 && mutualClosingFeeRate == 250) + assert(feeConfOfChannel(alice).feeTargets.mutualCloseBlockTarget == 2 && mutualClosingFeeRate == 250) assert(closing.feeSatoshis == expectedFirstProposedFee.amount) } @@ -332,8 +331,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { bob2alice.forward(alice) // agreeing on a closing fee val aliceCloseFee = alice2bob.expectMsgType[ClosingSigned].feeSatoshis - val nodeParams = bob.underlyingActor.nodeParams - nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(100)) + feeConfOfChannel(bob).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(100)) alice2bob.forward(bob) val bobCloseFee = bob2alice.expectMsgType[ClosingSigned].feeSatoshis bob2alice.forward(alice) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala index 27f1dd1058..41133460ea 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala @@ -26,6 +26,7 @@ import fr.acinq.bitcoin.{MilliSatoshi, Satoshi} import fr.acinq.eclair.TestConstants._ import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.{EclairWallet, TestWallet} +import fr.acinq.eclair.channel.states.StateTestsHelperMethods import fr.acinq.eclair.channel.{ChannelCreated, HasCommitments} import fr.acinq.eclair.crypto.TransportHandler import fr.acinq.eclair.io.Peer._ @@ -37,7 +38,7 @@ import scodec.bits.ByteVector import scala.concurrent.duration._ -class PeerSpec extends TestkitBaseClass { +class PeerSpec extends TestkitBaseClass with StateTestsHelperMethods { def ipv4FromInet4(address: InetSocketAddress) = IPv4.apply(address.getAddress.asInstanceOf[Inet4Address], address.getPort) @@ -249,10 +250,9 @@ class PeerSpec extends TestkitBaseClass { probe.send(peer, Peer.OpenChannel(remoteNodeId, Satoshi(12300), MilliSatoshi(0), None, None, None)) awaitCond(peer.stateData.channels.nonEmpty) - val nodeParams = peer.underlyingActor.nodeParams val channelCreated = probe.expectMsgType[ChannelCreated] - assert(channelCreated.initialFeeratePerKw == nodeParams.onChainFeeConf.feeEstimator.getFeeratePerKw(nodeParams.onChainFeeConf.feeTargets.commitmentBlockTarget)) - assert(channelCreated.fundingTxFeeratePerKw.get == nodeParams.onChainFeeConf.feeEstimator.getFeeratePerKw(nodeParams.onChainFeeConf.feeTargets.fundingBlockTarget)) + assert(channelCreated.initialFeeratePerKw == feeConfOfPeer(peer).feeEstimator.getFeeratePerKw(feeConfOfPeer(peer).feeTargets.commitmentBlockTarget)) + assert(channelCreated.fundingTxFeeratePerKw.get == feeConfOfPeer(peer).feeEstimator.getFeeratePerKw(feeConfOfPeer(peer).feeTargets.fundingBlockTarget)) } test("reply to ping") { f => From a75a931c0d56f6bb7a6adf5a7e193f79fa28702a Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 23 Jul 2019 17:37:32 +0200 Subject: [PATCH 18/25] Use block target = 12 as default for claim transactions --- eclair-core/src/main/resources/reference.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf index b08740cdb0..c9c7e4a98b 100644 --- a/eclair-core/src/main/resources/reference.conf +++ b/eclair-core/src/main/resources/reference.conf @@ -85,7 +85,7 @@ eclair { funding = 6 // target for the funding transaction commitment = 2 // target for the commitment transaction (used in force-close scenario) *do not change this unless you know what you are doing* mutual-close = 12 // target for the mutual close transaction - claim-main = 6 // target for the claim main transaction (tx that spends main channel output back to wallet) + claim-main = 12 // target for the claim main transaction (tx that spends main channel output back to wallet) } // maximum local vs remote feerate mismatch; 1.0 means 100% From f768ec5e54d81a714ca185a5d54abe914bbed628 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 24 Jul 2019 10:01:02 +0200 Subject: [PATCH 19/25] Move default-feerates configuration block inside on-chain-fees, renaming --- eclair-core/src/main/resources/reference.conf | 29 ++++++++++--------- .../scala/fr/acinq/eclair/NodeParams.scala | 10 +++---- .../main/scala/fr/acinq/eclair/Setup.scala | 14 ++++----- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf index c9c7e4a98b..97662a11d1 100644 --- a/eclair-core/src/main/resources/reference.conf +++ b/eclair-core/src/main/resources/reference.conf @@ -29,18 +29,6 @@ eclair { zmqtx = "tcp://127.0.0.1:29000" } - default-feerates { // those are in satoshis per kilobyte - delay-blocks { - 1 = 210000 - 2 = 180000 - 6 = 150000 - 12 = 110000 - 36 = 50000 - 72 = 20000 - 144 = 15000 - } - } - min-feerate = 3 // minimum feerate in satoshis per byte smooth-feerate-window = 6 // 1 = no smoothing @@ -80,8 +68,21 @@ eclair { fee-proportional-millionths = 100 // fee charged per transferred satoshi in millionths of a satoshi (100 = 0.01%) on-chain-fees { + + default-feerates { // those are in satoshis per kilobyte + delay-blocks { + 1 = 210000 + 2 = 180000 + 6 = 150000 + 12 = 110000 + 36 = 50000 + 72 = 20000 + 144 = 15000 + } + } + // number of blocks to target when computing fees for each transaction type - fee-targets { + target-blocks { funding = 6 // target for the funding transaction commitment = 2 // target for the commitment transaction (used in force-close scenario) *do not change this unless you know what you are doing* mutual-close = 12 // target for the mutual close transaction @@ -94,7 +95,7 @@ eclair { // 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 + update-fee-min-diff-ratio = 0.1 } revocation-timeout = 20 seconds // after sending a commit_sig, we will wait for at most that duration before disconnecting diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala index b0ce08c1de..4b41ac905d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala @@ -188,10 +188,10 @@ object NodeParams { .map(ip => NodeAddress.fromParts(ip, config.getInt("server.port")).get) ++ torAddress_opt val feeTargets = FeeTargets( - fundingBlockTarget = config.getInt("on-chain-fees.fee-targets.funding"), - commitmentBlockTarget = config.getInt("on-chain-fees.fee-targets.commitment"), - mutualCloseBlockTarget = config.getInt("on-chain-fees.fee-targets.mutual-close"), - claimMainBlockTarget = config.getInt("on-chain-fees.fee-targets.claim-main") + fundingBlockTarget = config.getInt("on-chain-fees.target-blocks.funding"), + commitmentBlockTarget = config.getInt("on-chain-fees.target-blocks.commitment"), + mutualCloseBlockTarget = config.getInt("on-chain-fees.target-blocks.mutual-close"), + claimMainBlockTarget = config.getInt("on-chain-fees.target-blocks.claim-main") ) NodeParams( @@ -224,7 +224,7 @@ object NodeParams { feeTargets = feeTargets, feeEstimator = feeEstimator, maxFeerateMismatch = config.getDouble("on-chain-fees.max-feerate-mismatch"), - updateFeeMinDiffRatio = config.getDouble("on-chain-fees.update-fee_min-diff-ratio") + updateFeeMinDiffRatio = config.getDouble("on-chain-fees.update-fee-min-diff-ratio") ), autoReconnect = config.getBoolean("auto-reconnect"), initialRandomReconnectDelay = FiniteDuration(config.getDuration("initial-random-reconnect-delay").getSeconds, TimeUnit.SECONDS), diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala index e6bc76d8b1..6adc1d15d1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala @@ -188,13 +188,13 @@ class Setup(datadir: File, defaultFeerates = { val confDefaultFeerates = FeeratesPerKB( - block_1 = config.getLong("default-feerates.delay-blocks.1"), - blocks_2 = config.getLong("default-feerates.delay-blocks.2"), - blocks_6 = config.getLong("default-feerates.delay-blocks.6"), - blocks_12 = config.getLong("default-feerates.delay-blocks.12"), - blocks_36 = config.getLong("default-feerates.delay-blocks.36"), - blocks_72 = config.getLong("default-feerates.delay-blocks.72"), - blocks_144 = config.getLong("default-feerates.delay-blocks.144") + block_1 = config.getLong("on-chain-fees.default-feerates.delay-blocks.1"), + blocks_2 = config.getLong("on-chain-fees.default-feerates.delay-blocks.2"), + blocks_6 = config.getLong("on-chain-fees.default-feerates.delay-blocks.6"), + blocks_12 = config.getLong("on-chain-fees.default-feerates.delay-blocks.12"), + blocks_36 = config.getLong("on-chain-fees.default-feerates.delay-blocks.36"), + blocks_72 = config.getLong("on-chain-fees.default-feerates.delay-blocks.72"), + blocks_144 = config.getLong("on-chain-fees.default-feerates.delay-blocks.144") ) Globals.feeratesPerKB.set(confDefaultFeerates) Globals.feeratesPerKw.set(FeeratesPerKw(confDefaultFeerates)) From 9da49969e7739712e2545aaadfb634cd12e2d1b0 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 24 Jul 2019 10:11:24 +0200 Subject: [PATCH 20/25] Remove helper methods in favor of pimp pattern --- .../channel/states/StateTestsHelperMethods.scala | 14 +++++++++++--- .../channel/states/e/NormalStateSpec.scala | 10 +++++----- .../channel/states/f/ShutdownStateSpec.scala | 4 ++-- .../channel/states/g/NegotiatingStateSpec.scala | 16 ++++++++-------- .../channel/states/h/ClosingStateSpec.scala | 8 ++++---- .../test/scala/fr/acinq/eclair/io/PeerSpec.scala | 4 ++-- 6 files changed, 32 insertions(+), 24 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala index 26110ed8fd..ac21eeba31 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala @@ -20,8 +20,9 @@ import java.util.UUID import akka.testkit.{TestFSMRef, TestKitBase, TestProbe} import fr.acinq.bitcoin.{ByteVector32, Crypto} -import fr.acinq.eclair.TestConstants.{Alice, Bob} +import fr.acinq.eclair.TestConstants.{Alice, Bob, TestFeeEstimator} import fr.acinq.eclair.blockchain._ +import fr.acinq.eclair.blockchain.fee.FeeTargets import fr.acinq.eclair.channel._ import fr.acinq.eclair.io.Peer import fr.acinq.eclair.payment.PaymentLifecycle @@ -162,7 +163,14 @@ trait StateTestsHelperMethods extends TestKitBase { def channelId(a: TestFSMRef[State, Data, Channel]) = Helpers.getChannelId(a.stateData) - def feeConfOfChannel(a: TestFSMRef[State, Data, Channel]) = a.underlyingActor.nodeParams.onChainFeeConf - def feeConfOfPeer(a: TestFSMRef[Peer.State, Peer.Data, Peer]) = a.underlyingActor.nodeParams.onChainFeeConf + implicit class ChannelWithTestFeeConf(a: TestFSMRef[State, Data, Channel]) { + def feeEstimator: TestFeeEstimator = a.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator] + def feeTargets: FeeTargets = a.underlyingActor.nodeParams.onChainFeeConf.feeTargets + } + + implicit class PeerWithTestFeeConf(a: TestFSMRef[Peer.State, Peer.Data, Peer]) { + def feeEstimator: TestFeeEstimator = a.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator] + def feeTargets: FeeTargets = a.underlyingActor.nodeParams.onChainFeeConf.feeTargets + } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 872c9bad5f..e327d9cad6 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -1393,7 +1393,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val sender = TestProbe() val fee = UpdateFee(ByteVector32.Zeroes, 100000000) // we first update the feerates so that we don't trigger a 'fee too different' error - feeConfOfChannel(bob).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(fee.feeratePerKw)) + bob.feeEstimator.setFeerate(FeeratesPerKw.single(fee.feeratePerKw)) sender.send(bob, fee) val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray) === CannotAffordFees(channelId(bob), missingSatoshis = 71620000L, reserveSatoshis = 20000L, feesSatoshis = 72400000L).getMessage) @@ -1407,11 +1407,11 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { test("recv UpdateFee (local/remote feerates are too different)") { f => import f._ - feeConfOfChannel(bob).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw(1000, 2000, 6000, 12000, 36000, 72000, 140000)) + bob.feeEstimator.setFeerate(FeeratesPerKw(1000, 2000, 6000, 12000, 36000, 72000, 140000)) val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx val sender = TestProbe() // Alice will use $localFeeRate when performing the checks for update_fee - val localFeeRate = feeConfOfChannel(bob).feeEstimator.getFeeratePerKw(feeConfOfChannel(bob).feeTargets.commitmentBlockTarget) + val localFeeRate = bob.feeEstimator.getFeeratePerKw(bob.feeTargets.commitmentBlockTarget) assert(localFeeRate === 2000) val remoteFeeUpdate = 85000 sender.send(bob, UpdateFee(ByteVector32.Zeroes, remoteFeeUpdate)) @@ -1430,7 +1430,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val bobCommitments = bob.stateData.asInstanceOf[DATA_NORMAL].commitments val tx = bobCommitments.localCommit.publishableTxs.commitTx.tx val sender = TestProbe() - val expectedFeeratePerKw = feeConfOfChannel(bob).feeEstimator.getFeeratePerKw(feeConfOfChannel(bob).feeTargets.commitmentBlockTarget) + val expectedFeeratePerKw = bob.feeEstimator.getFeeratePerKw(bob.feeTargets.commitmentBlockTarget) assert(bobCommitments.localCommit.spec.feeratePerKw == expectedFeeratePerKw) sender.send(bob, UpdateFee(ByteVector32.Zeroes, 252)) val error = bob2alice.expectMsgType[Error] @@ -1895,7 +1895,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get.claimHtlcTimeoutTxs.size == 2) // assert the feerate of the claim main is what we expect - val expectedFeeRate = feeConfOfChannel(alice).feeEstimator.getFeeratePerKw(feeConfOfChannel(alice).feeTargets.claimMainBlockTarget) + val expectedFeeRate = alice.feeEstimator.getFeeratePerKw(alice.feeTargets.claimMainBlockTarget) val expectedFee = Transactions.weight2fee(expectedFeeRate, Transactions.claimP2WPKHOutputWeight).toLong val claimFee = claimMain.txIn.map(in => bobCommitTx.txOut(in.outPoint.index.toInt).amount.toLong).sum - claimMain.txOut.map(_.amount.toLong).sum assert(claimFee == expectedFee) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala index d21ed49ce2..760efbe3f6 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala @@ -582,7 +582,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val sender = TestProbe() val fee = UpdateFee(ByteVector32.Zeroes, 100000000) // we first update the feerates so that we don't trigger a 'fee too different' error - feeConfOfChannel(bob).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(fee.feeratePerKw)) + bob.feeEstimator.setFeerate(FeeratesPerKw.single(fee.feeratePerKw)) sender.send(bob, fee) val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray) === CannotAffordFees(channelId(bob), missingSatoshis = 72120000L, reserveSatoshis = 20000L, feesSatoshis = 72400000L).getMessage) @@ -648,7 +648,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] val event = CurrentFeerates(FeeratesPerKw(100, 200, 600, 1200, 3600, 7200, 14400)) sender.send(alice, event) - alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, event.feeratesPerKw.feePerBlock(feeConfOfChannel(alice).feeTargets.commitmentBlockTarget))) + alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, event.feeratesPerKw.feePerBlock(alice.feeTargets.commitmentBlockTarget))) } test("recv CurrentFeerate (when funder, doesn't trigger an UpdateFee)") { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala index 0ff9603aa5..3b798fe887 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala @@ -53,12 +53,12 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods val sender = TestProbe() // alice initiates a closing if (test.tags.contains("fee2")) { - feeConfOfChannel(alice).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(4319)) - feeConfOfChannel(bob).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(4319)) + alice.feeEstimator.setFeerate(FeeratesPerKw.single(4319)) + bob.feeEstimator.setFeerate(FeeratesPerKw.single(4319)) } else { - feeConfOfChannel(alice).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(10000)) - feeConfOfChannel(bob).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(10000)) + alice.feeEstimator.setFeerate(FeeratesPerKw.single(10000)) + bob.feeEstimator.setFeerate(FeeratesPerKw.single(10000)) } sender.send(bob, CMD_CLOSE(None)) bob2alice.expectMsgType[Shutdown] @@ -69,11 +69,11 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods // In order to force a fee negotiation, we will change the current fee before forwarding // the Shutdown message to alice, so that alice computes a different initial closing fee. if (test.tags.contains("fee2")) { - feeConfOfChannel(alice).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(4316)) - feeConfOfChannel(bob).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(4316)) + alice.feeEstimator.setFeerate(FeeratesPerKw.single(4316)) + bob.feeEstimator.setFeerate(FeeratesPerKw.single(4316)) } else { - feeConfOfChannel(alice).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(5000)) - feeConfOfChannel(bob).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(5000)) + alice.feeEstimator.setFeerate(FeeratesPerKw.single(5000)) + bob.feeEstimator.setFeerate(FeeratesPerKw.single(5000)) } alice2bob.forward(bob) awaitCond(bob.stateName == NEGOTIATING) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala index 0abaf08862..b4715a7110 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala @@ -143,7 +143,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { test("start fee negotiation from configured block target") { f => import f._ - feeConfOfChannel(alice).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw(100, 250, 350, 450, 600, 800, 900)) + alice.feeEstimator.setFeerate(FeeratesPerKw(100, 250, 350, 450, 600, 800, 900)) val sender = TestProbe() // alice initiates a closing @@ -154,9 +154,9 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { bob2alice.forward(alice) val closing = alice2bob.expectMsgType[ClosingSigned] val aliceData = alice.stateData.asInstanceOf[DATA_NEGOTIATING] - val mutualClosingFeeRate = feeConfOfChannel(alice).feeEstimator.getFeeratePerKw(feeConfOfChannel(alice).feeTargets.mutualCloseBlockTarget) + val mutualClosingFeeRate = alice.feeEstimator.getFeeratePerKw(alice.feeTargets.mutualCloseBlockTarget) val expectedFirstProposedFee = Closing.firstClosingFee(aliceData.commitments, aliceData.localShutdown.scriptPubKey, aliceData.remoteShutdown.scriptPubKey, mutualClosingFeeRate)(akka.event.NoLogging) - assert(feeConfOfChannel(alice).feeTargets.mutualCloseBlockTarget == 2 && mutualClosingFeeRate == 250) + assert(alice.feeTargets.mutualCloseBlockTarget == 2 && mutualClosingFeeRate == 250) assert(closing.feeSatoshis == expectedFirstProposedFee.amount) } @@ -331,7 +331,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { bob2alice.forward(alice) // agreeing on a closing fee val aliceCloseFee = alice2bob.expectMsgType[ClosingSigned].feeSatoshis - feeConfOfChannel(bob).feeEstimator.asInstanceOf[TestFeeEstimator].setFeerate(FeeratesPerKw.single(100)) + bob.feeEstimator.setFeerate(FeeratesPerKw.single(100)) alice2bob.forward(bob) val bobCloseFee = bob2alice.expectMsgType[ClosingSigned].feeSatoshis bob2alice.forward(alice) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala index 41133460ea..76fd945de6 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala @@ -251,8 +251,8 @@ class PeerSpec extends TestkitBaseClass with StateTestsHelperMethods { awaitCond(peer.stateData.channels.nonEmpty) val channelCreated = probe.expectMsgType[ChannelCreated] - assert(channelCreated.initialFeeratePerKw == feeConfOfPeer(peer).feeEstimator.getFeeratePerKw(feeConfOfPeer(peer).feeTargets.commitmentBlockTarget)) - assert(channelCreated.fundingTxFeeratePerKw.get == feeConfOfPeer(peer).feeEstimator.getFeeratePerKw(feeConfOfPeer(peer).feeTargets.fundingBlockTarget)) + assert(channelCreated.initialFeeratePerKw == peer.feeEstimator.getFeeratePerKw(peer.feeTargets.commitmentBlockTarget)) + assert(channelCreated.fundingTxFeeratePerKw.get == peer.feeEstimator.getFeeratePerKw(peer.feeTargets.fundingBlockTarget)) } test("reply to ping") { f => From 5db1271cb40b7b82bfda47f5b03957ab49c7f651 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 24 Jul 2019 10:33:51 +0200 Subject: [PATCH 21/25] Rename default-feerates section, remove blank line --- eclair-core/src/main/resources/reference.conf | 19 ++++++++----------- .../main/scala/fr/acinq/eclair/Setup.scala | 14 +++++++------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf index 97662a11d1..cadbbec31b 100644 --- a/eclair-core/src/main/resources/reference.conf +++ b/eclair-core/src/main/resources/reference.conf @@ -68,17 +68,14 @@ eclair { fee-proportional-millionths = 100 // fee charged per transferred satoshi in millionths of a satoshi (100 = 0.01%) on-chain-fees { - - default-feerates { // those are in satoshis per kilobyte - delay-blocks { - 1 = 210000 - 2 = 180000 - 6 = 150000 - 12 = 110000 - 36 = 50000 - 72 = 20000 - 144 = 15000 - } + default-feerates { // those are per target block, in satoshis per kilobyte + 1 = 210000 + 2 = 180000 + 6 = 150000 + 12 = 110000 + 36 = 50000 + 72 = 20000 + 144 = 15000 } // number of blocks to target when computing fees for each transaction type diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala index 6adc1d15d1..bc8ee08f41 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala @@ -188,13 +188,13 @@ class Setup(datadir: File, defaultFeerates = { val confDefaultFeerates = FeeratesPerKB( - block_1 = config.getLong("on-chain-fees.default-feerates.delay-blocks.1"), - blocks_2 = config.getLong("on-chain-fees.default-feerates.delay-blocks.2"), - blocks_6 = config.getLong("on-chain-fees.default-feerates.delay-blocks.6"), - blocks_12 = config.getLong("on-chain-fees.default-feerates.delay-blocks.12"), - blocks_36 = config.getLong("on-chain-fees.default-feerates.delay-blocks.36"), - blocks_72 = config.getLong("on-chain-fees.default-feerates.delay-blocks.72"), - blocks_144 = config.getLong("on-chain-fees.default-feerates.delay-blocks.144") + block_1 = config.getLong("on-chain-fees.default-feerates.1"), + blocks_2 = config.getLong("on-chain-fees.default-feerates.2"), + blocks_6 = config.getLong("on-chain-fees.default-feerates.6"), + blocks_12 = config.getLong("on-chain-fees.default-feerates.12"), + blocks_36 = config.getLong("on-chain-fees.default-feerates.36"), + blocks_72 = config.getLong("on-chain-fees.default-feerates.72"), + blocks_144 = config.getLong("on-chain-fees.default-feerates.144") ) Globals.feeratesPerKB.set(confDefaultFeerates) Globals.feeratesPerKw.set(FeeratesPerKw(confDefaultFeerates)) From d240dfe4dcd4e0428bfaf81ec30e26951cdd10e2 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 24 Jul 2019 10:46:06 +0200 Subject: [PATCH 22/25] Move `OnChainFeeConf` to FeeEstimator.scala --- .../src/main/scala/fr/acinq/eclair/NodeParams.scala | 11 ++--------- .../fr/acinq/eclair/blockchain/fee/FeeEstimator.scala | 4 +++- .../test/scala/fr/acinq/eclair/TestConstants.scala | 4 ++-- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala index 4b41ac905d..9b67a89e8a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala @@ -24,8 +24,8 @@ import java.util.concurrent.TimeUnit import com.typesafe.config.{Config, ConfigFactory} import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.{Block, ByteVector32} -import fr.acinq.eclair.NodeParams.{OnChainFeeConf, WatcherType} -import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeTargets} +import fr.acinq.eclair.NodeParams.{WatcherType} +import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeTargets, OnChainFeeConf} import fr.acinq.eclair.channel.Channel import fr.acinq.eclair.crypto.KeyManager import fr.acinq.eclair.db._ @@ -90,13 +90,6 @@ object NodeParams { object ELECTRUM extends WatcherType - case class OnChainFeeConf( - feeTargets: FeeTargets, - feeEstimator: FeeEstimator, - maxFeerateMismatch: Double, - updateFeeMinDiffRatio: Double - ) - /** * Order of precedence for the configuration parameters: * 1) Java environment variables (-D...) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala index d896d39459..df18977172 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala @@ -24,4 +24,6 @@ trait FeeEstimator { } -case class FeeTargets(fundingBlockTarget: Int, commitmentBlockTarget: Int, mutualCloseBlockTarget: Int, claimMainBlockTarget: Int) \ No newline at end of file +case class FeeTargets(fundingBlockTarget: Int, commitmentBlockTarget: Int, mutualCloseBlockTarget: Int, claimMainBlockTarget: Int) + +case class OnChainFeeConf(feeTargets: FeeTargets, feeEstimator: FeeEstimator, maxFeerateMismatch: Double, updateFeeMinDiffRatio: Double) \ No newline at end of file diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala index 9224bef42d..c42a7a20ac 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala @@ -20,8 +20,8 @@ import java.sql.{Connection, DriverManager} import fr.acinq.bitcoin.Crypto.PrivateKey import fr.acinq.bitcoin.{Block, ByteVector32, Script} -import fr.acinq.eclair.NodeParams.{BITCOIND, OnChainFeeConf} -import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeTargets, FeeratesPerKw} +import fr.acinq.eclair.NodeParams.BITCOIND +import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeTargets, FeeratesPerKw, OnChainFeeConf} import fr.acinq.eclair.crypto.LocalKeyManager import fr.acinq.eclair.db._ import fr.acinq.eclair.io.Peer From 419bf600ac35ba20c5c000f2cef34c5649a124ac Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 24 Jul 2019 11:48:15 +0200 Subject: [PATCH 23/25] When computing the first mutual-closing fee use the minimum between the commitment fee and the configured fee target --- .../src/main/scala/fr/acinq/eclair/channel/Helpers.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index ca9df1b2d5..0320e14688 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -436,7 +436,7 @@ object Helpers { def firstClosingFee(commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): Satoshi = { // we "MUST set fee_satoshis less than or equal to the base fee of the final commitment transaction" val blockTarget = Math.max(feeTargets.mutualCloseBlockTarget, feeTargets.commitmentBlockTarget) - val feeratePerKw = feeEstimator.getFeeratePerKw(blockTarget) + val feeratePerKw = Math.min(feeEstimator.getFeeratePerKw(blockTarget), commitments.localCommit.spec.feeratePerKw) firstClosingFee(commitments, localScriptPubkey, remoteScriptPubkey, feeratePerKw) } From f350b876455d1ec15cc8b264838c30bc070c7961 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 25 Jul 2019 15:29:33 +0200 Subject: [PATCH 24/25] Do not compare confirmation targets when choosing a 'firstClosingFee', move comment closer to relevant line. --- .../src/main/scala/fr/acinq/eclair/channel/Helpers.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 0320e14688..5cbe075fcb 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -434,9 +434,9 @@ object Helpers { } def firstClosingFee(commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): Satoshi = { + val requestedFeerate = feeEstimator.getFeeratePerKw(feeTargets.mutualCloseBlockTarget) // we "MUST set fee_satoshis less than or equal to the base fee of the final commitment transaction" - val blockTarget = Math.max(feeTargets.mutualCloseBlockTarget, feeTargets.commitmentBlockTarget) - val feeratePerKw = Math.min(feeEstimator.getFeeratePerKw(blockTarget), commitments.localCommit.spec.feeratePerKw) + val feeratePerKw = Math.min(requestedFeerate, commitments.localCommit.spec.feeratePerKw) firstClosingFee(commitments, localScriptPubkey, remoteScriptPubkey, feeratePerKw) } From 8f1bcc0b1bd6733cfae54e88d20406f4f638d23f Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 25 Jul 2019 17:34:42 +0200 Subject: [PATCH 25/25] Move `OnChainFeeConf` value close to other on-chain fee related value --- .../scala/fr/acinq/eclair/NodeParams.scala | 14 +++++------ .../scala/fr/acinq/eclair/TestConstants.scala | 24 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala index 9b67a89e8a..55f5fb2859 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala @@ -48,6 +48,7 @@ case class NodeParams(keyManager: KeyManager, localFeatures: ByteVector, overrideFeatures: Map[PublicKey, (ByteVector, ByteVector)], dustLimitSatoshis: Long, + onChainFeeConf: OnChainFeeConf, maxHtlcValueInFlightMsat: UInt64, maxAcceptedHtlcs: Int, expiryDeltaBlocks: Int, @@ -65,7 +66,6 @@ case class NodeParams(keyManager: KeyManager, pingInterval: FiniteDuration, pingTimeout: FiniteDuration, pingDisconnect: Boolean, - onChainFeeConf: OnChainFeeConf, autoReconnect: Boolean, initialRandomReconnectDelay: FiniteDuration, maxReconnectInterval: FiniteDuration, @@ -196,6 +196,12 @@ object NodeParams { localFeatures = ByteVector.fromValidHex(config.getString("local-features")), overrideFeatures = overrideFeatures, dustLimitSatoshis = dustLimitSatoshis, + onChainFeeConf = OnChainFeeConf( + feeTargets = feeTargets, + feeEstimator = feeEstimator, + maxFeerateMismatch = config.getDouble("on-chain-fees.max-feerate-mismatch"), + updateFeeMinDiffRatio = config.getDouble("on-chain-fees.update-fee-min-diff-ratio") + ), maxHtlcValueInFlightMsat = UInt64(config.getLong("max-htlc-value-in-flight-msat")), maxAcceptedHtlcs = maxAcceptedHtlcs, expiryDeltaBlocks = expiryDeltaBlocks, @@ -213,12 +219,6 @@ object NodeParams { pingInterval = FiniteDuration(config.getDuration("ping-interval").getSeconds, TimeUnit.SECONDS), pingTimeout = FiniteDuration(config.getDuration("ping-timeout").getSeconds, TimeUnit.SECONDS), pingDisconnect = config.getBoolean("ping-disconnect"), - onChainFeeConf = OnChainFeeConf( - feeTargets = feeTargets, - feeEstimator = feeEstimator, - maxFeerateMismatch = config.getDouble("on-chain-fees.max-feerate-mismatch"), - updateFeeMinDiffRatio = config.getDouble("on-chain-fees.update-fee-min-diff-ratio") - ), autoReconnect = config.getBoolean("auto-reconnect"), initialRandomReconnectDelay = FiniteDuration(config.getDuration("initial-random-reconnect-delay").getSeconds, TimeUnit.SECONDS), maxReconnectInterval = FiniteDuration(config.getDuration("max-reconnect-interval").getSeconds, TimeUnit.SECONDS), diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala index c42a7a20ac..3f5e0b3744 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala @@ -71,6 +71,12 @@ object TestConstants { localFeatures = ByteVector(0), overrideFeatures = Map.empty, dustLimitSatoshis = 1100, + onChainFeeConf = OnChainFeeConf( + feeTargets = FeeTargets(6, 2, 2, 6), + feeEstimator = new TestFeeEstimator, + maxFeerateMismatch = 1.5, + updateFeeMinDiffRatio = 0.1 + ), maxHtlcValueInFlightMsat = UInt64(150000000), maxAcceptedHtlcs = 100, expiryDeltaBlocks = 144, @@ -88,12 +94,6 @@ object TestConstants { pingInterval = 30 seconds, pingTimeout = 10 seconds, pingDisconnect = true, - onChainFeeConf = OnChainFeeConf( - feeTargets = FeeTargets(6, 2, 2, 6), - feeEstimator = new TestFeeEstimator, - maxFeerateMismatch = 1.5, - updateFeeMinDiffRatio = 0.1 - ), autoReconnect = false, initialRandomReconnectDelay = 5 seconds, maxReconnectInterval = 1 hour, @@ -141,6 +141,12 @@ object TestConstants { localFeatures = ByteVector.empty, // no announcement overrideFeatures = Map.empty, dustLimitSatoshis = 1000, + onChainFeeConf = OnChainFeeConf( + feeTargets = FeeTargets(6, 2, 2, 6), + feeEstimator = new TestFeeEstimator, + maxFeerateMismatch = 1.0, + updateFeeMinDiffRatio = 0.1 + ), maxHtlcValueInFlightMsat = UInt64.MaxValue, // Bob has no limit on the combined max value of in-flight htlcs maxAcceptedHtlcs = 30, expiryDeltaBlocks = 144, @@ -158,12 +164,6 @@ object TestConstants { pingInterval = 30 seconds, pingTimeout = 10 seconds, pingDisconnect = true, - onChainFeeConf = OnChainFeeConf( - feeTargets = FeeTargets(6, 2, 2, 6), - feeEstimator = new TestFeeEstimator, - maxFeerateMismatch = 1.0, - updateFeeMinDiffRatio = 0.1 - ), autoReconnect = false, initialRandomReconnectDelay = 5 seconds, maxReconnectInterval = 1 hour,