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

Reject new static_remote_key channels (#2881)

* Reject new static_remote_key channels

We will still load and use existing static_remote_key channels, we can still open static_remote_key channels, but we will not accept new static_remote_key channels.
This behaviour can be overridden by setting `eclair.channel.accept-incoming-static-remote-key-channels` to `true`.

* Reject new obsolete incoming channels

We reject new incoming channels that don't even support `option_static_remotekey` (which is assumed to be on in the BOLTs).
Unit tests have been modified to use static_remote_key or anchor channels (default used to be the obsolete "standard" channel).

---------

Co-authored-by: Bastien Teinturier <31281497+t-bast@users.noreply.github.com>
This commit is contained in:
Fabrice Drouin 2024-07-16 17:11:41 +02:00 committed by GitHub
parent f8d6acb326
commit 86373b4411
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 248 additions and 191 deletions

View File

@ -17,6 +17,13 @@ To enable CoinGrinder at all fee rates and prevent the automatic consolidation o
consolidatefeerate=0
```
### Incoming obsolete channels will be rejected
Eclair will not allow remote peers to open new `static_remote_key` channels. These channels are obsolete, node operators should use `option_anchors` channels now.
Existing `static_remote_key` channels will continue to work. You can override this behaviour by setting `eclair.channel.accept-incoming-static-remote-key-channels` to true.
Eclair will not allow remote peers to open new obsolete channels that do not support `option_static_remotekey`.
### API changes
<insert changes>

View File

@ -163,6 +163,8 @@ eclair {
channel-opener-whitelist = [] // a list of public keys; we will ignore rate limits on pending channels from these peers
}
accept-incoming-static-remote-key-channels = false // whether we accept new incoming static_remote_key channels (which are obsolete, nodes should use anchor_output now)
quiescence-timeout = 1 minutes // maximum time we will stay quiescent (or wait to reach quiescence) before disconnecting
channel-update {

View File

@ -524,6 +524,7 @@ object NodeParams extends Logging {
quiescenceTimeout = FiniteDuration(config.getDuration("channel.quiescence-timeout").getSeconds, TimeUnit.SECONDS),
balanceThresholds = config.getConfigList("channel.channel-update.balance-thresholds").asScala.map(conf => BalanceThreshold(Satoshi(conf.getLong("available-sat")), Satoshi(conf.getLong("max-htlc-sat")))).toSeq,
minTimeBetweenUpdates = FiniteDuration(config.getDuration("channel.channel-update.min-time-between-updates").getSeconds, TimeUnit.SECONDS),
acceptIncomingStaticRemoteKeyChannels = config.getBoolean("channel.accept-incoming-static-remote-key-channels")
),
onChainFeeConf = OnChainFeeConf(
feeTargets = feeTargets,

View File

@ -103,7 +103,8 @@ object Channel {
remoteRbfLimits: RemoteRbfLimits,
quiescenceTimeout: FiniteDuration,
balanceThresholds: Seq[BalanceThreshold],
minTimeBetweenUpdates: FiniteDuration) {
minTimeBetweenUpdates: FiniteDuration,
acceptIncomingStaticRemoteKeyChannels: Boolean) {
require(0 <= maxHtlcValueInFlightPercent && maxHtlcValueInFlightPercent <= 100, "max-htlc-value-in-flight-percent must be between 0 and 100")
require(balanceThresholds.sortBy(_.available) == balanceThresholds, "channel-update.balance-thresholds must be sorted by available-sat")

View File

@ -148,6 +148,14 @@ private class OpenChannelInterceptor(peer: ActorRef[Any],
private def sanityCheckNonInitiator(request: OpenChannelNonInitiator): Behavior[Command] = {
validateRemoteChannelType(request.temporaryChannelId, request.channelFlags, request.channelType_opt, request.localFeatures, request.remoteFeatures) match {
case Right(_: ChannelTypes.Standard) =>
context.log.warn("rejecting incoming channel: anchor outputs must be used for new channels")
sendFailure("rejecting incoming channel: anchor outputs must be used for new channels", request)
waitForRequest()
case Right(_: ChannelTypes.StaticRemoteKey) if !nodeParams.channelConf.acceptIncomingStaticRemoteKeyChannels =>
context.log.warn("rejecting static_remote_key incoming static_remote_key channels")
sendFailure("rejecting incoming static_remote_key channel: anchor outputs must be used for new channels", request)
waitForRequest()
case Right(channelType) =>
val dualFunded = Features.canUseFeature(request.localFeatures, request.remoteFeatures, Features.DualFunding)
val upfrontShutdownScript = Features.canUseFeature(request.localFeatures, request.remoteFeatures, Features.UpfrontShutdownScript)

View File

@ -102,6 +102,7 @@ object TestConstants {
Wumbo -> Optional,
PaymentMetadata -> Optional,
RouteBlinding -> Optional,
StaticRemoteKey -> Mandatory
),
unknown = Set(UnknownFeature(TestFeature.optional))
),
@ -141,6 +142,7 @@ object TestConstants {
quiescenceTimeout = 2 minutes,
balanceThresholds = Nil,
minTimeBetweenUpdates = 0 hours,
acceptIncomingStaticRemoteKeyChannels = false
),
onChainFeeConf = OnChainFeeConf(
feeTargets = FeeTargets(funding = ConfirmationPriority.Medium, closing = ConfirmationPriority.Medium),
@ -271,6 +273,8 @@ object TestConstants {
Wumbo -> Optional,
PaymentMetadata -> Optional,
RouteBlinding -> Optional,
StaticRemoteKey -> Mandatory,
AnchorOutputsZeroFeeHtlcTx -> Optional
),
pluginParams = Nil,
overrideInitFeatures = Map.empty,
@ -308,6 +312,7 @@ object TestConstants {
quiescenceTimeout = 2 minutes,
balanceThresholds = Nil,
minTimeBetweenUpdates = 0 hour,
acceptIncomingStaticRemoteKeyChannels = false
),
onChainFeeConf = OnChainFeeConf(
feeTargets = FeeTargets(funding = ConfirmationPriority.Medium, closing = ConfirmationPriority.Medium),

View File

@ -9,14 +9,14 @@ import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{apply => _, _}
import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient
import fr.acinq.eclair.channel.Helpers.Closing.{CurrentRemoteClose, LocalClose}
import fr.acinq.eclair.channel.publish.TxPublisher.{PublishFinalTx, PublishReplaceableTx}
import fr.acinq.eclair.channel.states.ChannelStateTestsBase
import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags}
import fr.acinq.eclair.channel.{CLOSING, CMD_SIGN, DATA_CLOSING, DATA_NORMAL, Upstream}
import fr.acinq.eclair.db.jdbc.JdbcUtils.ExtendedResultSet._
import fr.acinq.eclair.db.pg.PgUtils.using
import fr.acinq.eclair.wire.internal.channel.ChannelCodecs.channelDataCodec
import fr.acinq.eclair.wire.protocol.{CommitSig, Error, RevokeAndAck, TlvStream, UpdateAddHtlc, UpdateAddHtlcTlv}
import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, MilliSatoshiLong, TestConstants, TestKitBaseClass, TimestampMilli, ToMilliSatoshiConversion, randomBytes32}
import org.scalatest.Outcome
import org.scalatest.{Outcome, Tag}
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
import org.sqlite.SQLiteConfig
@ -32,7 +32,7 @@ class CheckBalanceSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
type FixtureParam = SetupFixture
override def withFixture(test: OneArgTest): Outcome = {
val setup = init()
val setup = init(tags = test.tags)
within(30 seconds) {
reachNormal(setup, test.tags)
withFixture(test.toNoArgTest(setup))
@ -73,7 +73,7 @@ class CheckBalanceSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
)
}
test("take published remote commit tx into account") { f =>
test("take published remote commit tx into account", Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
// We add 3 htlcs Alice -> Bob (one of them below dust) and 2 htlcs Bob -> Alice
@ -89,9 +89,10 @@ class CheckBalanceSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// bob publishes his current commit tx
val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx
assert(bobCommitTx.txOut.size == 6) // two main outputs and 4 pending htlcs
assert(bobCommitTx.txOut.size == 8) // two anchor outputs, two main outputs and 4 pending htlcs
alice ! WatchFundingSpentTriggered(bobCommitTx)
// in response to that, alice publishes her claim txs
alice2blockchain.expectMsgType[PublishReplaceableTx] // claim-anchor
alice2blockchain.expectMsgType[PublishFinalTx] // claim-main
val claimHtlcTxs = (1 to 3).map(_ => alice2blockchain.expectMsgType[PublishReplaceableTx].txInfo.tx)
@ -114,7 +115,7 @@ class CheckBalanceSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
))
}
test("take published next remote commit tx into account") { f =>
test("take published next remote commit tx into account", Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
// We add 3 htlcs Alice -> Bob (one of them below dust) and 2 htlcs Bob -> Alice
@ -136,10 +137,11 @@ class CheckBalanceSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// as far as alice knows, bob currently has two valid unrevoked commitment transactions
// bob publishes his current commit tx
val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.active.last.localCommit.commitTxAndRemoteSig.commitTx.tx
assert(bobCommitTx.txOut.size == 5) // two main outputs and 3 pending htlcs
assert(bobCommitTx.txOut.size == 7) // two anchor outputs, two main outputs and 3 pending htlcs
alice ! WatchFundingSpentTriggered(bobCommitTx)
// in response to that, alice publishes her claim txs
alice2blockchain.expectMsgType[PublishReplaceableTx] // claim-anchor
alice2blockchain.expectMsgType[PublishFinalTx] // claim-main
val claimHtlcTxs = (1 to 2).map(_ => alice2blockchain.expectMsgType[PublishReplaceableTx].txInfo.tx)

View File

@ -21,7 +21,7 @@ import fr.acinq.bitcoin.scalacompat.{ByteVector32, OutPoint, SatoshiLong, Transa
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.WatchFundingSpentTriggered
import fr.acinq.eclair.channel.Helpers.Closing
import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.channel.states.ChannelStateTestsBase
import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags}
import fr.acinq.eclair.transactions.Transactions._
import fr.acinq.eclair.wire.protocol.{CommitSig, RevokeAndAck, UnknownNextPeer, UpdateAddHtlc}
import fr.acinq.eclair.{MilliSatoshiLong, NodeParams, TestKitBaseClass}
@ -261,13 +261,13 @@ class ChannelDataSpec extends TestKitBaseClass with AnyFunSuiteLike with Channel
case class RemoteFixture(bob: TestFSMRef[ChannelState, ChannelData, Channel], bobPendingHtlc: HtlcWithPreimage, remainingHtlcOutpoint: OutPoint, lcp: LocalCommitPublished, rcp: RemoteCommitPublished, claimHtlcTimeoutTxs: Seq[ClaimHtlcTimeoutTx], claimHtlcSuccessTxs: Seq[ClaimHtlcSuccessTx], probe: TestProbe)
private def setupClosingChannelForRemoteClose(): RemoteFixture = {
val f = setupClosingChannel()
val f = setupClosingChannel(Set(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs, ChannelStateTestsTags.StaticRemoteKey))
import f._
val bobClosing = bob.stateData.asInstanceOf[DATA_CLOSING]
assert(bobClosing.remoteCommitPublished.nonEmpty)
val rcp = bobClosing.remoteCommitPublished.get
assert(rcp.commitTx.txOut.length == 6)
assert(rcp.commitTx.txOut.length == 8)
assert(rcp.claimMainOutputTx.nonEmpty)
assert(rcp.claimHtlcTxs.size == 4) // we have one entry for each non-dust htlc
val claimHtlcTimeoutTxs = getClaimHtlcTimeoutTxs(rcp)
@ -390,10 +390,10 @@ class ChannelDataSpec extends TestKitBaseClass with AnyFunSuiteLike with Channel
assert(rcp4.isDone)
}
private def setupClosingChannelForNextRemoteClose(): RemoteFixture = {
private def setupClosingChannelForNextRemoteClose(tags: Set[String] = Set.empty): RemoteFixture = {
val probe = TestProbe()
val setup = init()
reachNormal(setup)
val setup = init(tags = tags)
reachNormal(setup, tags = tags)
import setup._
awaitCond(alice.stateName == NORMAL)
awaitCond(bob.stateName == NORMAL)
@ -431,7 +431,7 @@ class ChannelDataSpec extends TestKitBaseClass with AnyFunSuiteLike with Channel
val bobClosing = bob.stateData.asInstanceOf[DATA_CLOSING]
assert(bobClosing.nextRemoteCommitPublished.nonEmpty)
val rcp = bobClosing.nextRemoteCommitPublished.get
assert(rcp.commitTx.txOut.length == 6)
assert(rcp.commitTx.txOut.length == 8)
assert(rcp.claimMainOutputTx.nonEmpty)
assert(rcp.claimHtlcTxs.size == 4) // we have one entry for each non-dust htlc
val claimHtlcTimeoutTxs = getClaimHtlcTimeoutTxs(rcp)
@ -459,7 +459,7 @@ class ChannelDataSpec extends TestKitBaseClass with AnyFunSuiteLike with Channel
}
test("next remote commit published (our claim-HTLC txs are confirmed, they claim the remaining HTLC)") {
val f = setupClosingChannelForNextRemoteClose()
val f = setupClosingChannelForNextRemoteClose(Set(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs, ChannelStateTestsTags.StaticRemoteKey))
import f._
val rcp3 = (claimHtlcSuccessTxs ++ claimHtlcTimeoutTxs).map(_.tx).foldLeft(rcp) {
@ -474,7 +474,7 @@ class ChannelDataSpec extends TestKitBaseClass with AnyFunSuiteLike with Channel
}
test("next remote commit published (our claim-HTLC txs are confirmed and we claim the remaining HTLC)") {
val f = setupClosingChannelForNextRemoteClose()
val f = setupClosingChannelForNextRemoteClose(Set(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs, ChannelStateTestsTags.StaticRemoteKey))
import f._
val rcp3 = (claimHtlcSuccessTxs ++ claimHtlcTimeoutTxs).map(_.tx).foldLeft(rcp) {
@ -494,7 +494,7 @@ class ChannelDataSpec extends TestKitBaseClass with AnyFunSuiteLike with Channel
}
test("next remote commit published (they fulfill one of the HTLCs we sent them)") {
val f = setupClosingChannelForNextRemoteClose()
val f = setupClosingChannelForNextRemoteClose(Set(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs, ChannelStateTestsTags.StaticRemoteKey))
import f._
val remoteHtlcSuccess = lcp.htlcTxs.values.collectFirst { case Some(tx: HtlcSuccessTx) => tx }.get
@ -515,7 +515,7 @@ class ChannelDataSpec extends TestKitBaseClass with AnyFunSuiteLike with Channel
}
test("next remote commit published (they get back the HTLCs they sent us)") {
val f = setupClosingChannelForNextRemoteClose()
val f = setupClosingChannelForNextRemoteClose(Set(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs, ChannelStateTestsTags.StaticRemoteKey))
import f._
val rcp3 = claimHtlcTimeoutTxs.map(_.tx).foldLeft(rcp) {
@ -532,8 +532,8 @@ class ChannelDataSpec extends TestKitBaseClass with AnyFunSuiteLike with Channel
}
test("revoked commit published") {
val setup = init()
reachNormal(setup)
val setup = init(tags = Set(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs, ChannelStateTestsTags.StaticRemoteKey))
reachNormal(setup, tags = Set(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs, ChannelStateTestsTags.StaticRemoteKey))
import setup._
awaitCond(alice.stateName == NORMAL)
awaitCond(bob.stateName == NORMAL)

View File

@ -11,7 +11,7 @@ import fr.acinq.bitcoin.scalacompat._
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.WatchFundingSpentTriggered
import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.channel.states.ChannelStateTestsBase
import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags}
import fr.acinq.eclair.channel.states.ChannelStateTestsBase.FakeTxPublisherFactory
import fr.acinq.eclair.crypto.Generators
import fr.acinq.eclair.crypto.keymanager.ChannelKeyManager
@ -20,7 +20,7 @@ import fr.acinq.eclair.transactions.Scripts
import fr.acinq.eclair.transactions.Transactions.{ClaimP2WPKHOutputTx, DefaultCommitmentFormat, InputInfo, TxOwner}
import fr.acinq.eclair.wire.protocol.{ChannelReady, ChannelReestablish, ChannelUpdate, CommitSig, Error, Init, RevokeAndAck}
import fr.acinq.eclair.{TestKitBaseClass, _}
import org.scalatest.Outcome
import org.scalatest.{Outcome, Tag}
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
import scala.concurrent.duration._
@ -30,9 +30,9 @@ class RestoreSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Chan
type FixtureParam = SetupFixture
override def withFixture(test: OneArgTest): Outcome = {
val setup = init()
val setup = init(tags = test.tags)
within(30 seconds) {
reachNormal(setup)
reachNormal(setup, test.tags)
withFixture(test.toNoArgTest(setup))
}
}
@ -41,7 +41,7 @@ class RestoreSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Chan
private def bobInit = Init(Bob.nodeParams.features.initFeatures())
test("use funding pubkeys from publish commitment to spend our output") { f =>
test("use funding pubkeys from publish commitment to spend our output", Tag(ChannelStateTestsTags.StaticRemoteKey)) { f =>
import f._
val sender = TestProbe()
@ -96,37 +96,11 @@ class RestoreSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Chan
val OP_2 :: OP_PUSHDATA(pub1, _) :: OP_PUSHDATA(pub2, _) :: OP_2 :: OP_CHECKMULTISIG :: Nil = Script.parse(bobCommitTx.txIn(0).witness.stack.last)
// from Bob's commit tx we can also extract our p2wpkh output
val ourOutput = bobCommitTx.txOut.find(_.publicKeyScript.length == 22).get
val OP_0 :: OP_PUSHDATA(pubKeyHash, _) :: Nil = Script.parse(ourOutput.publicKeyScript)
val keyManager = Alice.nodeParams.channelKeyManager
// find our funding pub key
val fundingPubKey = Seq(PublicKey(pub1), PublicKey(pub2)).find {
pub =>
val channelKeyPath = ChannelKeyManager.keyPath(pub)
val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(channelKeyPath).publicKey, ce.myCurrentPerCommitmentPoint)
localPubkey.hash160 == pubKeyHash
} get
// compute our to-remote pubkey
val channelKeyPath = ChannelKeyManager.keyPath(fundingPubKey)
val ourToRemotePubKey = Generators.derivePubKey(keyManager.paymentPoint(channelKeyPath).publicKey, ce.myCurrentPerCommitmentPoint)
// spend our output
val tx = Transaction(version = 2,
txIn = TxIn(OutPoint(bobCommitTx, bobCommitTx.txOut.indexOf(ourOutput)), sequence = bitcoin.TxIn.SEQUENCE_FINAL, signatureScript = Nil) :: Nil,
txOut = TxOut(Satoshi(1000), Script.pay2pkh(fr.acinq.eclair.randomKey().publicKey)) :: Nil,
lockTime = 0)
val sig = keyManager.sign(
ClaimP2WPKHOutputTx(InputInfo(OutPoint(bobCommitTx, bobCommitTx.txOut.indexOf(ourOutput)), ourOutput, Script.pay2pkh(ourToRemotePubKey)), tx),
keyManager.paymentPoint(channelKeyPath),
ce.myCurrentPerCommitmentPoint,
TxOwner.Local,
DefaultCommitmentFormat)
val tx1 = tx.updateWitness(0, ScriptWitness(Scripts.der(sig) :: ourToRemotePubKey.value :: Nil))
Transaction.correctlySpends(tx1, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
// check that our output in Bob's commit tx sends to our static payment point
val Some(ourStaticPaymentPoint) = oldStateData.asInstanceOf[DATA_NORMAL].commitments.params.localParams.walletStaticPaymentBasepoint
assert(pubKeyHash == ourStaticPaymentPoint.hash160)
}
/** We are only interested in channel updates from Alice, we use the channel flag to discriminate */

View File

@ -1523,7 +1523,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
case Transactions.DefaultCommitmentFormat => ()
case _: AnchorOutputsCommitmentFormat => alice2blockchain.expectMsgType[PublishReplaceableTx] // claim anchor
}
alice2blockchain.expectMsgType[PublishFinalTx] // claim main output
if (!bob.stateData.asInstanceOf[DATA_NORMAL].commitments.params.channelFeatures.paysDirectlyToWallet) alice2blockchain.expectMsgType[PublishFinalTx] // claim main output
val claimHtlcSuccess = alice2blockchain.expectMsgType[PublishReplaceableTx]
assert(claimHtlcSuccess.txInfo.isInstanceOf[ClaimHtlcSuccessTx])
val claimHtlcSuccessTx = claimHtlcSuccess.txInfo.asInstanceOf[ClaimHtlcSuccessTx].copy(confirmationTarget = ConfirmationTarget.Absolute(overrideHtlcTarget))
@ -1532,7 +1532,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
val claimHtlcTimeoutTx = claimHtlcTimeout.txInfo.asInstanceOf[ClaimHtlcTimeoutTx].copy(confirmationTarget = ConfirmationTarget.Absolute(overrideHtlcTarget))
alice2blockchain.expectMsgType[WatchTxConfirmed] // commit tx
alice2blockchain.expectMsgType[WatchTxConfirmed] // claim main output
if (!bob.stateData.asInstanceOf[DATA_NORMAL].commitments.params.channelFeatures.paysDirectlyToWallet) alice2blockchain.expectMsgType[WatchTxConfirmed] // claim main output
alice2blockchain.expectMsgType[WatchOutputSpent] // claim-htlc-success tx
alice2blockchain.expectMsgType[WatchOutputSpent] // claim-htlc-timeout tx
alice2blockchain.expectNoMessage(100 millis)

View File

@ -80,7 +80,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS
val accept = bob2alice.expectMsgType[AcceptChannel]
// Since https://github.com/lightningnetwork/lightning-rfc/pull/714 we must include an empty upfront_shutdown_script.
assert(accept.upfrontShutdownScript_opt.contains(ByteVector.empty))
assert(accept.channelType_opt.contains(ChannelTypes.Standard()))
assert(accept.channelType_opt.contains(ChannelTypes.StaticRemoteKey()))
bob2alice.forward(alice)
awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL)
aliceOpenReplyTo.expectNoMessage()
@ -188,10 +188,10 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS
test("recv AcceptChannel (invalid channel type)") { f =>
import f._
val accept = bob2alice.expectMsgType[AcceptChannel]
assert(accept.channelType_opt.contains(ChannelTypes.Standard()))
assert(accept.channelType_opt.contains(ChannelTypes.StaticRemoteKey()))
val invalidAccept = accept.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputs())))
bob2alice.forward(alice, invalidAccept)
alice2bob.expectMsg(Error(accept.temporaryChannelId, "invalid channel_type=anchor_outputs, expected channel_type=standard"))
alice2bob.expectMsg(Error(accept.temporaryChannelId, "invalid channel_type=anchor_outputs, expected channel_type=static_remotekey"))
listener.expectMsgType[ChannelAborted]
awaitCond(alice.stateName == CLOSED)
aliceOpenReplyTo.expectMsgType[OpenChannelResponse.Rejected]

View File

@ -70,7 +70,7 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui
// Since https://github.com/lightningnetwork/lightning-rfc/pull/714 we must include an empty upfront_shutdown_script.
assert(open.upfrontShutdownScript_opt.contains(ByteVector.empty))
// We always send a channel type, even for standard channels.
assert(open.channelType_opt.contains(ChannelTypes.Standard()))
assert(open.channelType_opt.contains(ChannelTypes.StaticRemoteKey()))
alice2bob.forward(bob)
awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED)
assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].params.commitmentFormat == DefaultCommitmentFormat)

View File

@ -23,7 +23,7 @@ import fr.acinq.bitcoin.scalacompat.{ByteVector32, Transaction}
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.channel.publish.TxPublisher
import fr.acinq.eclair.channel.publish.{TxPublisher, TxPublisherSpec}
import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags}
import fr.acinq.eclair.payment.relay.Relayer.RelayFees
import fr.acinq.eclair.router.Announcements
@ -254,11 +254,12 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu
awaitCond(alice.stateName == NORMAL)
}
test("recv WatchFundingSpentTriggered (remote commit)") { f =>
test("recv WatchFundingSpentTriggered (remote commit)", Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
// bob publishes his commitment tx
val tx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx
alice ! WatchFundingSpentTriggered(tx)
alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx]
alice2blockchain.expectMsgType[TxPublisher.PublishTx]
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == tx.txid)
aliceListener.expectMsgType[ChannelAborted]

View File

@ -619,7 +619,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
awaitCond(alice.stateName == WAIT_FOR_DUAL_FUNDING_READY)
}
test("recv WatchFundingSpentTriggered while offline (remote commit)", Tag(ChannelStateTestsTags.DualFunding)) { f =>
test("recv WatchFundingSpentTriggered while offline (remote commit)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].latestFundingTx.sharedTx.asInstanceOf[FullySignedSharedTransaction].signedTx
alice ! INPUT_DISCONNECTED
@ -637,6 +637,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
val bobCommitTx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx
alice ! WatchFundingSpentTriggered(bobCommitTx.tx)
aliceListener.expectMsgType[TransactionPublished]
val claimAnchor = alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx]
val claimMain = alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx]
assert(claimMain.input.txid == bobCommitTx.tx.txid)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobCommitTx.tx.txid)
@ -645,7 +646,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
awaitCond(alice.stateName == CLOSING)
}
test("recv WatchFundingSpentTriggered while offline (previous tx)", Tag(ChannelStateTestsTags.DualFunding)) { f =>
test("recv WatchFundingSpentTriggered while offline (previous tx)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
val fundingTx1 = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].latestFundingTx.sharedTx.asInstanceOf[FullySignedSharedTransaction].signedTx
@ -670,6 +671,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
// Bob broadcasts his commit tx.
alice ! WatchFundingSpentTriggered(bobCommitTx1)
assert(aliceListener.expectMsgType[TransactionPublished].tx.txid == bobCommitTx1.txid)
val claimAnchor = alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx]
val claimMain = alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx]
assert(claimMain.input.txid == bobCommitTx1.txid)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobCommitTx1.txid)
@ -678,7 +680,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
awaitCond(alice.stateName == CLOSING)
}
test("recv WatchFundingSpentTriggered after restart (remote commit)", Tag(ChannelStateTestsTags.DualFunding)) { f =>
test("recv WatchFundingSpentTriggered after restart (remote commit)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
val aliceData = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED]
@ -690,6 +692,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
assert(aliceListener.expectMsgType[TransactionConfirmed].tx == fundingTx)
assert(alice2blockchain.expectMsgType[WatchFundingSpent].txId == fundingTx.txid)
alice2 ! WatchFundingSpentTriggered(bobData.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx)
val claimAnchorAlice = alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx]
val claimMainAlice = alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx]
assert(claimMainAlice.input.txid == bobData.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txid)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobData.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txid)
@ -700,6 +703,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
assert(bobListener.expectMsgType[TransactionConfirmed].tx == fundingTx)
assert(bob2blockchain.expectMsgType[WatchFundingSpent].txId == fundingTx.txid)
bob2 ! WatchFundingSpentTriggered(aliceData.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx)
val claimAnchorBob = bob2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx]
val claimMainBob = bob2blockchain.expectMsgType[TxPublisher.PublishFinalTx]
assert(claimMainBob.input.txid == aliceData.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txid)
assert(bob2blockchain.expectMsgType[WatchTxConfirmed].txId == aliceData.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txid)
@ -707,7 +711,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
awaitCond(bob2.stateName == CLOSING)
}
test("recv WatchFundingSpentTriggered after restart (previous tx)", Tag(ChannelStateTestsTags.DualFunding)) { f =>
test("recv WatchFundingSpentTriggered after restart (previous tx)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
val fundingTx1 = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].latestFundingTx.sharedTx.asInstanceOf[FullySignedSharedTransaction].signedTx
@ -726,6 +730,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
assert(aliceListener.expectMsgType[TransactionConfirmed].tx == fundingTx1)
assert(alice2blockchain.expectMsgType[WatchFundingSpent].txId == fundingTx1.txid)
alice2 ! WatchFundingSpentTriggered(bobCommitTx1)
val claimAnchorAlice = alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx]
val claimMainAlice = alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx]
assert(claimMainAlice.input.txid == bobCommitTx1.txid)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobCommitTx1.txid)
@ -736,6 +741,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
assert(bobListener.expectMsgType[TransactionConfirmed].tx == fundingTx1)
assert(bob2blockchain.expectMsgType[WatchFundingSpent].txId == fundingTx1.txid)
bob2 ! WatchFundingSpentTriggered(aliceCommitTx1)
val claimAnchorBob = bob2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx]
val claimMainBob = bob2blockchain.expectMsgType[TxPublisher.PublishFinalTx]
assert(claimMainBob.input.txid == aliceCommitTx1.txid)
assert(bob2blockchain.expectMsgType[WatchTxConfirmed].txId == aliceCommitTx1.txid)
@ -887,13 +893,14 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == tx.txid)
}
test("recv Error (remote commit published)", Tag(ChannelStateTestsTags.DualFunding)) { f =>
test("recv Error (remote commit published)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx
alice ! Error(ByteVector32.Zeroes, "force-closing channel, bye-bye")
awaitCond(alice.stateName == CLOSING)
aliceListener.expectMsgType[ChannelAborted]
assert(alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx].tx.txid == aliceCommitTx.txid)
val claimAnchorLocal = alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx]
val claimMainLocal = alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx]
assert(claimMainLocal.input.txid == aliceCommitTx.txid)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == aliceCommitTx.txid)
@ -901,13 +908,15 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
// Bob broadcasts his commit tx as well.
val bobCommitTx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx
alice ! WatchFundingSpentTriggered(bobCommitTx)
alice2blockchain.expectMsgType[WatchOutputSpent]
val claimAnchorRemote = alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx]
val claimMainRemote = alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx]
assert(claimMainRemote.input.txid == bobCommitTx.txid)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobCommitTx.txid)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == claimMainRemote.tx.txid)
}
test("recv Error (previous tx confirms)", Tag(ChannelStateTestsTags.DualFunding)) { f =>
test("recv Error (previous tx confirms)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
val fundingTx1 = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].latestFundingTx.sharedTx.asInstanceOf[FullySignedSharedTransaction].signedTx
@ -925,6 +934,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
awaitCond(alice.stateName == CLOSING)
aliceListener.expectMsgType[ChannelAborted]
assert(alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx].tx.txid == aliceCommitTx2.tx.txid)
val claimAnchor2 = alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx]
val claimMain2 = alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx]
assert(claimMain2.input.txid == aliceCommitTx2.tx.txid)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == aliceCommitTx2.tx.txid)
@ -933,8 +943,10 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
// A previous funding transaction confirms, so Alice publishes the corresponding commit tx.
alice ! WatchFundingConfirmedTriggered(BlockHeight(42000), 42, fundingTx1)
assert(aliceListener.expectMsgType[TransactionConfirmed].tx == fundingTx1)
alice2blockchain.expectMsgType[WatchOutputSpent]
assert(alice2blockchain.expectMsgType[WatchFundingSpent].txId == fundingTx1.txid)
assert(alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx].tx.txid == aliceCommitTx1.tx.txid)
val claimAnchor1 = alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx]
val claimMain1 = alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx]
assert(claimMain1.input.txid == aliceCommitTx1.tx.txid)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == aliceCommitTx1.tx.txid)
@ -942,6 +954,8 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
// Bob publishes his commit tx, Alice reacts by spending her remote main output.
alice ! WatchFundingSpentTriggered(bobCommitTx1.tx)
alice2blockchain.expectMsgType[WatchOutputSpent]
val claimAnchorRemote = alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx]
val claimMainRemote = alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx]
assert(claimMainRemote.input.txid == bobCommitTx1.tx.txid)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobCommitTx1.tx.txid)

View File

@ -60,7 +60,8 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF
alice.underlying.system.eventStream.subscribe(listener.ref, classOf[ChannelClosed])
bob.underlying.system.eventStream.subscribe(listener.ref, classOf[ChannelAborted])
bob.underlying.system.eventStream.subscribe(listener.ref, classOf[ChannelClosed])
alice ! INPUT_INIT_CHANNEL_INITIATOR(ByteVector32.Zeroes, TestConstants.fundingSatoshis, dualFunded = false, TestConstants.feeratePerKw, TestConstants.feeratePerKw, fundingTxFeeBudget_opt = None, Some(pushMsat), requireConfirmedInputs = false, aliceParams, alice2bob.ref, bobInit, channelFlags, channelConfig, channelType, replyTo = aliceOpenReplyTo.ref.toTyped)
val commitTxFeerate = if (test.tags.contains(ChannelStateTestsTags.AnchorOutputs) || test.tags.contains(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) TestConstants.anchorOutputsFeeratePerKw else TestConstants.feeratePerKw
alice ! INPUT_INIT_CHANNEL_INITIATOR(ByteVector32.Zeroes, TestConstants.fundingSatoshis, dualFunded = false, commitTxFeerate, commitTxFeerate, fundingTxFeeBudget_opt = None, Some(pushMsat), requireConfirmedInputs = false, aliceParams, alice2bob.ref, bobInit, channelFlags, channelConfig, channelType, replyTo = aliceOpenReplyTo.ref.toTyped)
alice2blockchain.expectMsgType[TxPublisher.SetChannelId]
bob ! INPUT_INIT_CHANNEL_NON_INITIATOR(ByteVector32.Zeroes, None, dualFunded = false, None, bobParams, bob2alice.ref, aliceInit, channelConfig, channelType)
bob2blockchain.expectMsgType[TxPublisher.SetChannelId]
@ -230,13 +231,14 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF
awaitCond(bob.stateName == CLOSED)
}
test("recv WatchFundingSpentTriggered (remote commit)") { f =>
test("recv WatchFundingSpentTriggered (remote commit)", Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
// bob publishes his commitment tx
val tx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx
alice ! WatchFundingSpentTriggered(tx)
assert(listener.expectMsgType[TransactionPublished].tx == tx)
alice2blockchain.expectMsgType[TxPublisher.PublishTx]
alice2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx]
alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx]
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == tx.txid)
listener.expectMsgType[ChannelAborted]
awaitCond(alice.stateName == CLOSING)

View File

@ -1573,6 +1573,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
alice ! CMD_FORCECLOSE(ActorRef.noSender)
alice2bob.expectMsgType[Error]
val aliceCommitTx2 = assertPublished(alice2blockchain, "commit-tx")
assertPublished(alice2blockchain, "local-anchor")
assertPublished(alice2blockchain, "local-main-delayed")
// alice publishes her htlc timeout transactions
val htlcsTxsOut = htlcs.aliceToBob.map(_ => assertPublished(alice2blockchain, "htlc-timeout"))
@ -1580,6 +1581,8 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
alice2blockchain.expectMsgType[WatchTxConfirmed]
alice2blockchain.expectMsgType[WatchTxConfirmed]
alice2blockchain.expectMsgType[WatchOutputSpent]
// watch for all htlc outputs from local commit-tx to be spent
htlcs.aliceToBob.map(_ => alice2blockchain.expectMsgType[WatchOutputSpent])
htlcs.bobToAlice.map(_ => alice2blockchain.expectMsgType[WatchOutputSpent])
@ -1599,7 +1602,8 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
watchAlternativeConfirmed.replyTo ! WatchAlternativeCommitTxConfirmedTriggered(BlockHeight(400000), 42, bobCommitTx1)
// we're back to the normal handling of remote commit
val claimMain = assertPublished(alice2blockchain, "remote-main")
assertPublished(alice2blockchain, "local-anchor")
val claimMain = assertPublished(alice2blockchain, "remote-main-delayed")
val htlcsTxsOut1 = htlcs.aliceToBob.map(_ => assertPublished(alice2blockchain, "claim-htlc-timeout"))
htlcsTxsOut1.foreach(tx => Transaction.correctlySpends(tx, Seq(bobCommitTx1), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS))
val watchConfirmedRemoteCommit = alice2blockchain.expectWatchTxConfirmed(bobCommitTx1.txid)
@ -1629,11 +1633,11 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
assert(Helpers.Closing.isClosed(alice.stateData.asInstanceOf[DATA_CLOSING], None).exists(_.isInstanceOf[RemoteClose]))
}
test("force-close with multiple splices (previous active remote)") { f =>
test("force-close with multiple splices (previous active remote)", Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
testForceCloseWithMultipleSplicesPreviousActiveRemote(f)
}
test("force-close with multiple splices (previous active remote, quiescence)", Tag(Quiescence)) { f =>
test("force-close with multiple splices (previous active remote, quiescence)", Tag(Quiescence), Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
testForceCloseWithMultipleSplicesPreviousActiveRemote(f)
}
@ -1671,17 +1675,19 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
// alice watches bob's revoked commit tx, and force-closes with her latest commitment
assert(alice2blockchain.expectMsgType[WatchAlternativeCommitTxConfirmed].txId == bobRevokedCommitTx.txid)
val aliceCommitTx2 = assertPublished(alice2blockchain, "commit-tx")
assertPublished(alice2blockchain, "local-anchor")
val claimMainDelayed2 = assertPublished(alice2blockchain, "local-main-delayed")
htlcs.aliceToBob.map(_ => assertPublished(alice2blockchain, "htlc-timeout"))
alice2blockchain.expectWatchTxConfirmed(aliceCommitTx2.txid)
alice2blockchain.expectWatchTxConfirmed(claimMainDelayed2.txid)
alice2blockchain.expectMsgType[WatchOutputSpent]
htlcs.aliceToBob.map(_ => alice2blockchain.expectMsgType[WatchOutputSpent])
htlcs.bobToAlice.map(_ => alice2blockchain.expectMsgType[WatchOutputSpent])
// bob's revoked tx wins
alice ! WatchAlternativeCommitTxConfirmedTriggered(BlockHeight(400000), 42, bobRevokedCommitTx)
// alice reacts by punishing bob
val aliceClaimMain = assertPublished(alice2blockchain, "remote-main")
val aliceClaimMain = assertPublished(alice2blockchain, "remote-main-delayed")
val aliceMainPenalty = assertPublished(alice2blockchain, "main-penalty")
val aliceHtlcsPenalty = htlcs.aliceToBob.map(_ => assertPublished(alice2blockchain, "htlc-penalty")) ++ htlcs.bobToAlice.map(_ => assertPublished(alice2blockchain, "htlc-penalty"))
aliceHtlcsPenalty.foreach(tx => Transaction.correctlySpends(tx, Seq(bobRevokedCommitTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS))
@ -1707,11 +1713,11 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
assert(Helpers.Closing.isClosed(alice.stateData.asInstanceOf[DATA_CLOSING], None).exists(_.isInstanceOf[RevokedClose]))
}
test("force-close with multiple splices (previous active revoked)") { f =>
test("force-close with multiple splices (previous active revoked)", Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
testForceCloseWithMultipleSplicesPreviousActiveRevoked(f)
}
test("force-close with multiple splices (previous active revoked, quiescent)", Tag(Quiescence)) { f =>
test("force-close with multiple splices (previous active revoked, quiescent)", Tag(Quiescence), Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
testForceCloseWithMultipleSplicesPreviousActiveRevoked(f)
}

View File

@ -3075,7 +3075,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
awaitCond(bob.stateName == CLOSING)
}
test("recv WatchFundingSpentTriggered (their commit w/ htlc)") { f =>
test("recv WatchFundingSpentTriggered (their commit w/ htlc)", Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
val (ra1, htlca1) = addHtlc(250000000 msat, CltvExpiryDelta(50), alice, bob, alice2bob, bob2alice)
@ -3100,10 +3100,11 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// bob publishes his current commit tx
val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx
assert(bobCommitTx.txOut.size == 6) // two main outputs and 4 pending htlcs
assert(bobCommitTx.txOut.size == 8) // two anchor outputs, two main outputs and 4 pending htlcs
alice ! WatchFundingSpentTriggered(bobCommitTx)
// in response to that, alice publishes her claim txs
alice2blockchain.expectMsgType[PublishReplaceableTx]
val claimMain = alice2blockchain.expectMsgType[PublishFinalTx].tx
// in addition to her 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 claimHtlcTxs = (1 to 3).map(_ => alice2blockchain.expectMsgType[PublishReplaceableTx])
@ -3115,7 +3116,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
}).sum
// at best we have a little less than 450 000 + 250 000 + 100 000 + 50 000 = 850 000 (because fees)
val amountClaimed = claimMain.txOut.head.amount + htlcAmountClaimed
assert(amountClaimed == 814880.sat)
assert(amountClaimed == 823680.sat)
// alice sets the confirmation targets to the HTLC expiry
assert(claimHtlcTxs.collect { case PublishReplaceableTx(tx: ClaimHtlcSuccessTx, _) => (tx.htlcId, tx.confirmationTarget.confirmBefore) }.toMap == Map(htlcb1.id -> htlcb1.cltvExpiry.blockHeight))
@ -3138,9 +3139,9 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// assert the feerate of the claim main is what we expect
val expectedFeeRate = alice.underlyingActor.nodeParams.onChainFeeConf.getClosingFeerate(alice.underlyingActor.nodeParams.currentFeerates)
val expectedFee = Transactions.weight2fee(expectedFeeRate, Transactions.claimP2WPKHOutputWeight)
val claimFee = claimMain.txIn.map(in => bobCommitTx.txOut(in.outPoint.index.toInt).amount).sum - claimMain.txOut.map(_.amount).sum
assert(claimFee == expectedFee)
val claimFeeRate = Transactions.fee2rate(claimFee, claimMain.weight())
assert(claimFeeRate >= expectedFeeRate * 0.9 && claimFeeRate <= expectedFeeRate * 1.2)
}
test("recv WatchFundingSpentTriggered (their commit w/ pending unsigned htlcs)") { f =>
@ -3159,7 +3160,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(addSettled.htlc == htlc1)
}
test("recv WatchFundingSpentTriggered (their *next* commit w/ htlc)") { f =>
test("recv WatchFundingSpentTriggered (their *next* commit w/ htlc)", Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
val (ra1, htlca1) = addHtlc(250000000 msat, CltvExpiryDelta(24), alice, bob, alice2bob, bob2alice)
@ -3191,10 +3192,11 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// bob publishes his current commit tx
val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx
assert(bobCommitTx.txOut.size == 5) // two main outputs and 3 pending htlcs
assert(bobCommitTx.txOut.size == 7) // two anchor outputs, two main outputs and 3 pending htlcs
alice ! WatchFundingSpentTriggered(bobCommitTx)
// in response to that, alice publishes her claim txs
val claimAnchor = alice2blockchain.expectMsgType[PublishReplaceableTx].txInfo.tx
val claimMain = alice2blockchain.expectMsgType[PublishFinalTx].tx
// in addition to her main output, alice can only claim 2 out of 3 htlcs, she can't do anything regarding the htlc sent by bob for which she does not have the preimage
val claimHtlcTxs = (1 to 2).map(_ => alice2blockchain.expectMsgType[PublishReplaceableTx])
@ -3206,7 +3208,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
}).sum
// at best we have a little less than 500 000 + 250 000 + 100 000 = 850 000 (because fees)
val amountClaimed = claimMain.txOut.head.amount + htlcAmountClaimed
assert(amountClaimed == 822310.sat)
assert(amountClaimed == 829850.sat)
// alice sets the confirmation targets to the HTLC expiry
assert(claimHtlcTxs.collect { case PublishReplaceableTx(tx: ClaimHtlcTimeoutTx, _) => (tx.htlcId, tx.confirmationTarget.confirmBefore) }.toMap == Map(htlca1.id -> htlca1.cltvExpiry.blockHeight, htlca2.id -> htlca2.cltvExpiry.blockHeight))
@ -3248,7 +3250,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(addSettled.htlc == htlc2)
}
test("recv WatchFundingSpentTriggered (revoked commit)") { f =>
test("recv WatchFundingSpentTriggered (revoked commit)", Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
// initially we have :
// alice = 800 000
@ -3273,8 +3275,8 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// a->b = 10 000
// a->b = 10 000
// a->b = 10 000
// two main outputs + 4 htlc
assert(revokedTx.txOut.size == 6)
// two anchor outputs + two main outputs + 4 htlc
assert(revokedTx.txOut.size == 8)
alice ! WatchFundingSpentTriggered(revokedTx)
alice2bob.expectMsgType[Error]
@ -3294,18 +3296,18 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
htlcPenaltyTxs.foreach(htlcPenaltyTx => Transaction.correctlySpends(htlcPenaltyTx, Seq(revokedTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS))
// two main outputs are 760 000 and 200 000
assert(mainTx.txOut.head.amount == 741500.sat)
assert(mainTx.txOut.head.amount == 750390.sat)
assert(mainPenaltyTx.txOut.head.amount == 195160.sat)
assert(htlcPenaltyTxs(0).txOut.head.amount == 4540.sat)
assert(htlcPenaltyTxs(1).txOut.head.amount == 4540.sat)
assert(htlcPenaltyTxs(2).txOut.head.amount == 4540.sat)
assert(htlcPenaltyTxs(3).txOut.head.amount == 4540.sat)
assert(htlcPenaltyTxs(0).txOut.head.amount == 4510.sat)
assert(htlcPenaltyTxs(1).txOut.head.amount == 4510.sat)
assert(htlcPenaltyTxs(2).txOut.head.amount == 4510.sat)
assert(htlcPenaltyTxs(3).txOut.head.amount == 4510.sat)
awaitCond(alice.stateName == CLOSING)
assert(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.size == 1)
}
test("recv WatchFundingSpentTriggered (revoked commit with identical htlcs)") { f =>
test("recv WatchFundingSpentTriggered (revoked commit with identical htlcs)", Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
val sender = TestProbe()
@ -3338,7 +3340,9 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// bob = 200 000
// a->b = 10 000
// a->b = 10 000
assert(revokedTx.txOut.size == 4)
// local anchor -> 330
// remote anchor -> 330
assert(revokedTx.txOut.size == 6)
alice ! WatchFundingSpentTriggered(revokedTx)
alice2bob.expectMsgType[Error]

View File

@ -29,7 +29,7 @@ import fr.acinq.eclair.blockchain.{CurrentBlockHeight, CurrentFeerates}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.channel.publish.TxPublisher.{PublishFinalTx, PublishReplaceableTx, PublishTx}
import fr.acinq.eclair.channel.states.ChannelStateTestsBase
import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags}
import fr.acinq.eclair.channel.states.ChannelStateTestsBase.PimpTestFSM
import fr.acinq.eclair.transactions.Transactions.{ClaimHtlcTimeoutTx, HtlcSuccessTx}
import fr.acinq.eclair.wire.protocol._
@ -55,10 +55,10 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
override def withFixture(test: OneArgTest): Outcome = {
val aliceParams = Alice.nodeParams
.modify(_.onChainFeeConf.closeOnOfflineMismatch).setToIf(test.tags.contains(DisableOfflineMismatch))(false)
val setup = init(nodeParamsA = aliceParams)
val setup = init(nodeParamsA = aliceParams, tags = test.tags)
import setup._
within(30 seconds) {
reachNormal(setup)
reachNormal(setup, tags = test.tags)
if (test.tags.contains(IgnoreChannelUpdates)) {
setup.alice2bob.ignoreMsg({ case _: ChannelUpdate => true })
setup.bob2alice.ignoreMsg({ case _: ChannelUpdate => true })
@ -289,7 +289,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommitIndex == 4)
}
test("reconnect with an outdated commitment", Tag(IgnoreChannelUpdates)) { f =>
test("reconnect with an outdated commitment", Tag(IgnoreChannelUpdates), Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
val (ra1, htlca1) = addHtlc(250000000 msat, alice, bob, alice2bob, bob2alice)
@ -339,7 +339,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
Transaction.correctlySpends(claimMainOutput, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
}
test("reconnect with an outdated commitment (but counterparty can't tell)", Tag(IgnoreChannelUpdates)) { f =>
test("reconnect with an outdated commitment (but counterparty can't tell)", Tag(IgnoreChannelUpdates), Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
// we start by storing the current state
@ -394,7 +394,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
Transaction.correctlySpends(claimMainOutput, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
}
test("counterparty lies about having a more recent commitment and publishes current commitment", Tag(IgnoreChannelUpdates)) { f =>
test("counterparty lies about having a more recent commitment and publishes current commitment", Tag(IgnoreChannelUpdates), Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
// the current state contains a pending htlc
@ -421,6 +421,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
alice ! WatchFundingSpentTriggered(bobCommitTx)
// alice is able to claim her main output and the htlc (once it times out)
alice2blockchain.expectMsgType[PublishReplaceableTx].txInfo.tx
val claimMainOutput = alice2blockchain.expectMsgType[PublishFinalTx].tx
Transaction.correctlySpends(claimMainOutput, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
val claimHtlc = alice2blockchain.expectMsgType[PublishReplaceableTx]
@ -428,7 +429,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
Transaction.correctlySpends(claimHtlc.txInfo.tx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
}
test("counterparty lies about having a more recent commitment and publishes revoked commitment", Tag(IgnoreChannelUpdates)) { f =>
test("counterparty lies about having a more recent commitment and publishes revoked commitment", Tag(IgnoreChannelUpdates), Tag(ChannelStateTestsTags.StaticRemoteKey), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
// we sign a new commitment to make sure the first one is revoked
@ -455,7 +456,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
alice ! WatchFundingSpentTriggered(bobRevokedCommitTx)
// alice is able to claim all outputs
assert(bobRevokedCommitTx.txOut.length == 2)
assert(bobRevokedCommitTx.txOut.length == 4)
val claimMainOutput = alice2blockchain.expectMsgType[PublishFinalTx].tx
Transaction.correctlySpends(claimMainOutput, bobRevokedCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
val claimRevokedOutput = alice2blockchain.expectMsgType[PublishFinalTx].tx

View File

@ -31,6 +31,7 @@ import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsT
import fr.acinq.eclair.payment._
import fr.acinq.eclair.payment.relay.Relayer._
import fr.acinq.eclair.payment.send.SpontaneousRecipient
import fr.acinq.eclair.transactions.Transactions.ClaimLocalAnchorOutputTx
import fr.acinq.eclair.wire.protocol.{AnnouncementSignatures, ClosingSigned, CommitSig, Error, FailureMessageCodecs, PermanentChannelFailure, RevokeAndAck, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFee, UpdateFulfillHtlc}
import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, MilliSatoshiLong, TestConstants, TestKitBaseClass, randomBytes32}
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
@ -51,7 +52,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
val r2 = randomBytes32()
override def withFixture(test: OneArgTest): Outcome = {
val setup = init()
val setup = init(tags = test.tags)
import setup._
within(30 seconds) {
reachNormal(setup, test.tags)
@ -715,14 +716,16 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
awaitCond(bob.stateName == CLOSING)
}
test("recv WatchFundingSpentTriggered (their commit)") { f =>
test("recv WatchFundingSpentTriggered (their commit)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.StaticRemoteKey)) { f =>
import f._
// bob publishes his current commit tx, which contains two pending htlcs alice->bob
val bobCommitTx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx
assert(bobCommitTx.txOut.size == 4) // two main outputs and 2 pending htlcs
assert(bobCommitTx.txOut.size == 6) // two main outputs and 2 pending htlcs
alice ! WatchFundingSpentTriggered(bobCommitTx)
// in response to that, alice publishes her claim txs
val anchorTx = alice2blockchain.expectMsgType[PublishReplaceableTx]
assert(anchorTx.txInfo.isInstanceOf[ClaimLocalAnchorOutputTx])
val claimMain = alice2blockchain.expectMsgType[PublishFinalTx].tx
// in addition to her main output, alice can only claim 2 out of 3 htlcs, she can't do anything regarding the htlc sent by bob for which she does not have the preimage
val claimHtlcTxs = (1 to 2).map(_ => alice2blockchain.expectMsgType[PublishReplaceableTx].txInfo.tx)
@ -734,7 +737,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
}).sum
// htlc will timeout and be eventually refunded so we have a little less than fundingSatoshis - pushMsat = 1000000 - 200000 = 800000 (because fees)
val amountClaimed = htlcAmountClaimed + claimMain.txOut.head.amount
assert(amountClaimed == 774040.sat)
assert(amountClaimed == 780290.sat)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobCommitTx.txid)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == claimMain.txid)
@ -750,7 +753,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
assert(getClaimHtlcTimeoutTxs(rcp).length == 2)
}
test("recv WatchFundingSpentTriggered (their next commit)") { f =>
test("recv WatchFundingSpentTriggered (their next commit)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.StaticRemoteKey)) { f =>
import f._
// bob fulfills the first htlc
fulfillHtlc(0, r1, bob, alice, bob2alice, alice2bob)
@ -768,10 +771,11 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
// bob publishes his current commit tx, which contains one pending htlc alice->bob
val bobCommitTx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx
assert(bobCommitTx.txOut.size == 3) // two main outputs and 1 pending htlc
assert(bobCommitTx.txOut.size == 5) // two anchor outputs, two main outputs and 1 pending htlc
alice ! WatchFundingSpentTriggered(bobCommitTx)
// in response to that, alice publishes her claim txs
alice2blockchain.expectMsgType[PublishReplaceableTx].txInfo.tx // claim local anchor output
val claimTxs = Seq(
alice2blockchain.expectMsgType[PublishFinalTx].tx,
// there is only one htlc to claim in the commitment bob published
@ -784,7 +788,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
claimTx.txOut.head.amount
}).sum
// htlc will timeout and be eventually refunded so we have a little less than fundingSatoshis - pushMsat - htlc1 = 1000000 - 200000 - 300 000 = 500000 (because fees)
assert(amountClaimed == 481210.sat)
assert(amountClaimed == 486200.sat)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobCommitTx.txid)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == claimTxs(0).txid)
@ -799,11 +803,11 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
assert(getClaimHtlcTimeoutTxs(rcp).length == 1)
}
test("recv WatchFundingSpentTriggered (revoked tx)") { f =>
test("recv WatchFundingSpentTriggered (revoked tx)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.StaticRemoteKey)) { f =>
import f._
val revokedTx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx
// two main outputs + 2 htlc
assert(revokedTx.txOut.size == 4)
assert(revokedTx.txOut.size == 6)
// bob fulfills one of the pending htlc (just so that he can have a new sig)
fulfillHtlc(0, r1, bob, alice, bob2alice, alice2bob)
@ -832,25 +836,25 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
Transaction.correctlySpends(htlc2PenaltyTx, Seq(revokedTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
// two main outputs are 300 000 and 200 000, htlcs are 300 000 and 200 000
assert(mainTx.txOut.head.amount == 284940.sat)
assert(mainTx.txOut.head.amount == 291250.sat)
assert(mainPenaltyTx.txOut.head.amount == 195160.sat)
assert(htlc1PenaltyTx.txOut.head.amount == 194540.sat)
assert(htlc2PenaltyTx.txOut.head.amount == 294540.sat)
assert(htlc1PenaltyTx.txOut.head.amount == 194510.sat)
assert(htlc2PenaltyTx.txOut.head.amount == 294510.sat)
awaitCond(alice.stateName == CLOSING)
assert(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.size == 1)
}
test("recv WatchFundingSpentTriggered (revoked tx with updated commitment)") { f =>
test("recv WatchFundingSpentTriggered (revoked tx with updated commitment)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.StaticRemoteKey)) { f =>
import f._
val initialCommitTx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx
assert(initialCommitTx.txOut.size == 4) // two main outputs + 2 htlc
assert(initialCommitTx.txOut.size == 6) // two main outputs + 2 htlc
// bob fulfills one of the pending htlc (commitment update while in shutdown state)
fulfillHtlc(0, r1, bob, alice, bob2alice, alice2bob)
crossSign(bob, alice, bob2alice, alice2bob)
val revokedTx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx
assert(revokedTx.txOut.size == 3) // two main outputs + 1 htlc
assert(revokedTx.txOut.size == 5) // two anchor outputs, two main outputs + 1 htlc
// bob fulfills the second pending htlc (and revokes the previous commitment)
fulfillHtlc(1, r2, bob, alice, bob2alice, alice2bob)
@ -875,9 +879,9 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
Transaction.correctlySpends(htlcPenaltyTx, Seq(revokedTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
// two main outputs are 300 000 and 200 000, htlcs are 300 000 and 200 000
assert(mainTx.txOut(0).amount == 286660.sat)
assert(mainTx.txOut(0).amount == 291680.sat)
assert(mainPenaltyTx.txOut(0).amount == 495160.sat)
assert(htlcPenaltyTx.txOut(0).amount == 194540.sat)
assert(htlcPenaltyTx.txOut(0).amount == 194510.sat)
awaitCond(alice.stateName == CLOSING)
assert(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.size == 1)

View File

@ -21,7 +21,9 @@ import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.bitcoin.ScriptFlags
import fr.acinq.bitcoin.scalacompat.Crypto.PrivateKey
import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, OutPoint, SatoshiLong, Script, Transaction, TxIn, TxOut}
import fr.acinq.eclair.Features.StaticRemoteKey
import fr.acinq.eclair.TestUtils.randomTxId
import fr.acinq.eclair.blockchain.DummyOnChainWallet
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._
import fr.acinq.eclair.blockchain.fee.{ConfirmationPriority, ConfirmationTarget, FeeratePerKw, FeeratesPerKw}
import fr.acinq.eclair.channel._
@ -316,7 +318,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
}
test("recv WatchFundingSpentTriggered (mutual close before converging)") { f =>
testMutualCloseBeforeConverge(f, ChannelFeatures())
testMutualCloseBeforeConverge(f, ChannelFeatures(Features.StaticRemoteKey))
}
test("recv WatchFundingSpentTriggered (mutual close before converging, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f =>
@ -471,7 +473,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
}
test("recv WatchTxConfirmedTriggered (local commit)") { f =>
testLocalCommitTxConfirmed(f, ChannelFeatures())
testLocalCommitTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey))
}
test("recv WatchTxConfirmedTriggered (local commit, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f =>
@ -774,7 +776,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// actual test starts here
channelUpdateListener.expectMsgType[LocalChannelDown]
assert(closingState.claimMainOutputTx.nonEmpty)
assert(closingState.claimMainOutputTx.isEmpty)
assert(closingState.claimHtlcTxs.isEmpty)
// when the commit tx is signed, alice knows that the htlc she sent right before the unilateral close will never reach the chain
alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, bobCommitTx)
@ -825,7 +827,8 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val bobCommitTx = bobCommitTxs.last.commitTx.tx
assert(bobCommitTx.txOut.size == 2) // two main outputs
val closingState = remoteClose(bobCommitTx, alice, alice2blockchain)
assert(closingState.claimMainOutputTx.nonEmpty)
assert(bobCommitTx.txOut.exists(_.publicKeyScript == Script.write(Script.pay2wpkh(DummyOnChainWallet.dummyReceivePubkey)))) // bob's commit tx sends directly to our wallet
assert(closingState.claimMainOutputTx.isEmpty)
assert(closingState.claimHtlcTxs.isEmpty)
assert(alice.stateData.asInstanceOf[DATA_CLOSING].copy(remoteCommitPublished = None) == initialState)
val txPublished = txListener.expectMsgType[TransactionPublished]
@ -961,7 +964,8 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(claimHtlcTimeoutTxs.length == 3)
alice ! WatchTxConfirmedTriggered(BlockHeight(42), 0, bobCommitTx)
alice ! WatchTxConfirmedTriggered(BlockHeight(45), 0, closingState.claimMainOutputTx.get.tx)
// for static_remote_key channels there is no claimMainOutputTx (bob's commit tx directly sends to our wallet)
closingState.claimMainOutputTx.foreach(claimMainOutputTx => alice ! WatchTxConfirmedTriggered(BlockHeight(45), 0, claimMainOutputTx.tx))
alice2relayer.expectNoMessage(100 millis)
alice ! WatchTxConfirmedTriggered(BlockHeight(201), 0, claimHtlcTimeoutTxs(0))
val forwardedFail1 = alice2relayer.expectMsgType[RES_ADD_SETTLED[Origin, HtlcResult.OnChainFail]].htlc
@ -977,7 +981,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
}
test("recv WatchTxConfirmedTriggered (remote commit with multiple htlcs for the same payment)") { f =>
testRemoteCommitTxWithHtlcsConfirmed(f, ChannelFeatures())
testRemoteCommitTxWithHtlcsConfirmed(f, ChannelFeatures(Features.StaticRemoteKey))
}
test("recv WatchTxConfirmedTriggered (remote commit with multiple htlcs for the same payment, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f =>
@ -1004,14 +1008,14 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx
assert(bobCommitTx.txOut.length == 3) // two main outputs + 1 HTLC
val closingState = remoteClose(bobCommitTx, alice, alice2blockchain)
assert(closingState.claimMainOutputTx.nonEmpty)
assert(closingState.claimMainOutputTx.isEmpty)
assert(bobCommitTx.txOut.exists(_.publicKeyScript == Script.write(Script.pay2wpkh(DummyOnChainWallet.dummyReceivePubkey))))
assert(closingState.claimHtlcTxs.size == 1)
assert(getClaimHtlcSuccessTxs(closingState).isEmpty) // we don't have the preimage to claim the htlc-success yet
assert(getClaimHtlcTimeoutTxs(closingState).isEmpty)
// Alice receives the preimage for the first HTLC from downstream; she can now claim the corresponding HTLC output.
alice ! CMD_FULFILL_HTLC(htlc1.id, r1, commit = true)
assert(alice2blockchain.expectMsgType[PublishFinalTx].tx == closingState.claimMainOutputTx.get.tx)
val claimHtlcSuccessTx = getClaimHtlcSuccessTxs(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get).head.tx
Transaction.correctlySpends(claimHtlcSuccessTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
val publishHtlcSuccessTx = alice2blockchain.expectMsgType[PublishReplaceableTx].txInfo.asInstanceOf[ClaimHtlcSuccessTx]
@ -1020,7 +1024,6 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// Alice resets watches on all relevant transactions.
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobCommitTx.txid)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == closingState.claimMainOutputTx.get.tx.txid)
val watchHtlcSuccess = alice2blockchain.expectMsgType[WatchOutputSpent]
assert(watchHtlcSuccess.txId == bobCommitTx.txid)
assert(watchHtlcSuccess.outputIndex == claimHtlcSuccessTx.txIn.head.outPoint.index)
@ -1029,9 +1032,8 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, bobCommitTx)
// The second htlc was not included in the commit tx published on-chain, so we can consider it failed
assert(alice2relayer.expectMsgType[RES_ADD_SETTLED[Origin, HtlcResult.OnChainFail]].htlc == htlc2)
alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, closingState.claimMainOutputTx.get.tx)
alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, claimHtlcSuccessTx)
assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get.irrevocablySpent.values.toSet == Set(bobCommitTx, closingState.claimMainOutputTx.get.tx, claimHtlcSuccessTx))
assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get.irrevocablySpent.values.toSet == Set(bobCommitTx, claimHtlcSuccessTx))
awaitCond(alice.stateName == CLOSED)
alice2blockchain.expectNoMessage(100 millis)
alice2relayer.expectNoMessage(100 millis)
@ -1097,14 +1099,14 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
test("recv WatchTxConfirmedTriggered (next remote commit)") { f =>
import f._
val (bobCommitTx, closingState, htlcs) = testNextRemoteCommitTxConfirmed(f, ChannelFeatures())
val (bobCommitTx, closingState, htlcs) = testNextRemoteCommitTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey))
val txPublished = txListener.expectMsgType[TransactionPublished]
assert(txPublished.tx == bobCommitTx)
assert(txPublished.miningFee > 0.sat) // alice is funder, she pays the fee for the remote commit
val claimHtlcTimeoutTxs = getClaimHtlcTimeoutTxs(closingState).map(_.tx)
alice ! WatchTxConfirmedTriggered(BlockHeight(42), 0, bobCommitTx)
assert(txListener.expectMsgType[TransactionConfirmed].tx == bobCommitTx)
alice ! WatchTxConfirmedTriggered(BlockHeight(45), 0, closingState.claimMainOutputTx.get.tx)
closingState.claimMainOutputTx.foreach(claimMainOutputTx => alice ! WatchTxConfirmedTriggered(BlockHeight(45), 0, claimMainOutputTx.tx))
alice2relayer.expectNoMessage(100 millis)
alice ! WatchTxConfirmedTriggered(BlockHeight(201), 0, claimHtlcTimeoutTxs(0))
val forwardedFail1 = alice2relayer.expectMsgType[RES_ADD_SETTLED[Origin, HtlcResult.OnChainFail]].htlc
@ -1144,7 +1146,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val (bobCommitTx, closingState, htlcs) = testNextRemoteCommitTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx))
val claimHtlcTimeoutTxs = getClaimHtlcTimeoutTxs(closingState).map(_.tx)
alice ! WatchTxConfirmedTriggered(BlockHeight(42), 0, bobCommitTx)
alice ! WatchTxConfirmedTriggered(BlockHeight(45), 0, closingState.claimMainOutputTx.get.tx)
closingState.claimMainOutputTx.foreach(claimMainOutputTx => alice ! WatchTxConfirmedTriggered(BlockHeight(45), 0, claimMainOutputTx.tx))
alice2relayer.expectNoMessage(100 millis)
alice ! WatchTxConfirmedTriggered(BlockHeight(201), 0, claimHtlcTimeoutTxs(0))
val forwardedFail1 = alice2relayer.expectMsgType[RES_ADD_SETTLED[Origin, HtlcResult.OnChainFail]].htlc
@ -1178,7 +1180,11 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx
assert(bobCommitTx.txOut.length == 4) // two main outputs + 2 HTLCs
val closingState = remoteClose(bobCommitTx, alice, alice2blockchain)
assert(closingState.claimMainOutputTx.nonEmpty)
if (!bob.stateData.asInstanceOf[DATA_NORMAL].commitments.params.channelFeatures.paysDirectlyToWallet) {
assert(closingState.claimMainOutputTx.nonEmpty)
} else {
assert(closingState.claimMainOutputTx.isEmpty)
}
assert(closingState.claimHtlcTxs.size == 2)
assert(getClaimHtlcSuccessTxs(closingState).isEmpty) // we don't have the preimage to claim the htlc-success yet
assert(getClaimHtlcTimeoutTxs(closingState).length == 1)
@ -1186,7 +1192,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// Alice receives the preimage for the first HTLC from downstream; she can now claim the corresponding HTLC output.
alice ! CMD_FULFILL_HTLC(htlc1.id, r1, commit = true)
assert(alice2blockchain.expectMsgType[PublishFinalTx].tx == closingState.claimMainOutputTx.get.tx)
closingState.claimMainOutputTx.foreach(claimMainOutputTx => assert(alice2blockchain.expectMsgType[PublishFinalTx].tx == claimMainOutputTx.tx))
val claimHtlcSuccessTx = getClaimHtlcSuccessTxs(alice.stateData.asInstanceOf[DATA_CLOSING].nextRemoteCommitPublished.get).head.tx
Transaction.correctlySpends(claimHtlcSuccessTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
val publishHtlcSuccessTx = alice2blockchain.expectMsgType[PublishReplaceableTx].txInfo.asInstanceOf[ClaimHtlcSuccessTx]
@ -1197,14 +1203,14 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(publishHtlcTimeoutTx.confirmationTarget == ConfirmationTarget.Absolute(htlc2.cltvExpiry.blockHeight))
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobCommitTx.txid)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == closingState.claimMainOutputTx.get.tx.txid)
closingState.claimMainOutputTx.foreach(claimMainOutputTx => assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == claimMainOutputTx.tx.txid))
val watchHtlcs = alice2blockchain.expectMsgType[WatchOutputSpent] :: alice2blockchain.expectMsgType[WatchOutputSpent] :: Nil
watchHtlcs.foreach(ws => assert(ws.txId == bobCommitTx.txid))
assert(watchHtlcs.map(_.outputIndex).toSet == Set(claimHtlcSuccessTx, claimHtlcTimeoutTx).map(_.txIn.head.outPoint.index))
alice2blockchain.expectNoMessage(100 millis)
alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, bobCommitTx)
alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, closingState.claimMainOutputTx.get.tx)
closingState.claimMainOutputTx.foreach(claimMainOutputTx => alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, claimMainOutputTx.tx))
alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, claimHtlcSuccessTx)
alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, claimHtlcTimeoutTx)
assert(alice2relayer.expectMsgType[RES_ADD_SETTLED[Origin, HtlcResult.OnChainFail]].htlc == htlc2)
@ -1284,22 +1290,18 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
test("recv WatchTxConfirmedTriggered (future remote commit)") { f =>
import f._
val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, ChannelFeatures())
val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey))
val txPublished = txListener.expectMsgType[TransactionPublished]
assert(txPublished.tx == bobCommitTx)
assert(txPublished.miningFee > 0.sat) // alice is funder, she pays the fee for the remote commit
// alice is able to claim its main output
val claimMainTx = alice2blockchain.expectMsgType[PublishFinalTx].tx
Transaction.correctlySpends(claimMainTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
// bob's commit tx sends directly to alice's wallet
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobCommitTx.txid)
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].futureRemoteCommitPublished.isDefined)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == claimMainTx.txid)
alice2blockchain.expectNoMessage(250 millis) // alice ignores the htlc-timeout
// actual test starts here
alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, bobCommitTx)
assert(txListener.expectMsgType[TransactionConfirmed].tx == bobCommitTx)
alice ! WatchTxConfirmedTriggered(BlockHeight(0), 0, claimMainTx)
awaitCond(alice.stateName == CLOSED)
}
@ -1333,7 +1335,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
test("recv INPUT_RESTORED (future remote commit)") { f =>
import f._
val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, ChannelFeatures())
val bobCommitTx = testFutureRemoteCommitTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey))
// simulate a node restart
val beforeRestart = alice.stateData.asInstanceOf[DATA_CLOSING]
@ -1341,11 +1343,8 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
alice ! INPUT_RESTORED(beforeRestart)
awaitCond(alice.stateName == CLOSING)
// then we should claim our main output
val claimMainTx = alice2blockchain.expectMsgType[PublishFinalTx].tx
Transaction.correctlySpends(claimMainTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
// bob's commit tx sends funds directly to our wallet
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == bobCommitTx.txid)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == claimMainTx.txid)
}
case class RevokedCloseFixture(bobRevokedTxs: Seq[LocalCommit], htlcsAlice: Seq[(UpdateAddHtlc, ByteVector32)], htlcsBob: Seq[(UpdateAddHtlc, ByteVector32)])
@ -1488,10 +1487,6 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
awaitCond(alice.stateName == CLOSED)
}
test("recv WatchFundingSpentTriggered (one revoked tx)") { f =>
testFundingSpentRevokedTx(f, ChannelFeatures())
}
test("recv WatchFundingSpentTriggered (one revoked tx, option_static_remotekey)", Tag(ChannelStateTestsTags.StaticRemoteKey)) { f =>
testFundingSpentRevokedTx(f, ChannelFeatures(Features.StaticRemoteKey))
}
@ -1506,7 +1501,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
test("recv WatchFundingSpentTriggered (multiple revoked tx)") { f =>
import f._
val revokedCloseFixture = prepareRevokedClose(f, ChannelFeatures())
val revokedCloseFixture = prepareRevokedClose(f, ChannelFeatures(Features.StaticRemoteKey))
assert(revokedCloseFixture.bobRevokedTxs.map(_.commitTxAndRemoteSig.commitTx.tx.txid).toSet.size == revokedCloseFixture.bobRevokedTxs.size) // all commit txs are distinct
def broadcastBobRevokedTx(revokedTx: Transaction, htlcCount: Int, revokedCount: Int): RevokedCommitPublished = {
@ -1516,14 +1511,14 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.last.commitTx == revokedTx)
// alice publishes penalty txs
val claimMain = alice2blockchain.expectMsgType[PublishFinalTx].tx
val mainPenalty = alice2blockchain.expectMsgType[PublishFinalTx].tx
val claimMain_opt = if (!alice.stateData.asInstanceOf[DATA_CLOSING].commitments.params.channelFeatures.paysDirectlyToWallet) Some(alice2blockchain.expectMsgType[PublishFinalTx].tx) else None
val htlcPenaltyTxs = (1 to htlcCount).map(_ => alice2blockchain.expectMsgType[PublishFinalTx].tx)
(claimMain +: mainPenalty +: htlcPenaltyTxs).foreach(tx => Transaction.correctlySpends(tx, revokedTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS))
(mainPenalty +: (claimMain_opt.toList ++ htlcPenaltyTxs)).foreach(tx => Transaction.correctlySpends(tx, revokedTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS))
// alice watches confirmation for the outputs only her can claim
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == revokedTx.txid)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == claimMain.txid)
claimMain_opt.foreach(claimMain => assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == claimMain.txid))
// alice watches outputs that can be spent by both parties
assert(alice2blockchain.expectMsgType[WatchOutputSpent].outputIndex == mainPenalty.txIn.head.outPoint.index)
@ -1544,7 +1539,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// bob's second revoked tx confirms: once all penalty txs are confirmed, alice can move to the closed state
// NB: if multiple txs confirm in the same block, we may receive the events in any order
alice ! WatchTxConfirmedTriggered(BlockHeight(100), 1, rvk2.mainPenaltyTx.get.tx)
alice ! WatchTxConfirmedTriggered(BlockHeight(100), 2, rvk2.claimMainOutputTx.get.tx)
rvk2.claimMainOutputTx.foreach(claimMainOutputTx => alice ! WatchTxConfirmedTriggered(BlockHeight(100), 2, claimMainOutputTx.tx))
alice ! WatchTxConfirmedTriggered(BlockHeight(100), 3, rvk2.commitTx)
alice ! WatchTxConfirmedTriggered(BlockHeight(115), 0, rvk2.htlcPenaltyTxs(0).tx)
assert(alice.stateName == CLOSING)
@ -1577,7 +1572,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
}
test("recv INPUT_RESTORED (one revoked tx)") { f =>
testInputRestoredRevokedTx(f, ChannelFeatures())
testInputRestoredRevokedTx(f, ChannelFeatures(Features.StaticRemoteKey))
}
test("recv INPUT_RESTORED (one revoked tx, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f =>
@ -1693,10 +1688,6 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
awaitCond(alice.stateName == CLOSED)
}
test("recv WatchOutputSpentTriggered (one revoked tx, counterparty published htlc-success tx)") { f =>
testOutputSpentRevokedTx(f, ChannelFeatures())
}
test("recv WatchOutputSpentTriggered (one revoked tx, counterparty published htlc-success tx, option_static_remotekey)", Tag(ChannelStateTestsTags.StaticRemoteKey)) { f =>
testOutputSpentRevokedTx(f, ChannelFeatures(Features.StaticRemoteKey))
}
@ -1836,7 +1827,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
}
test("recv WatchTxConfirmedTriggered (one revoked tx, pending htlcs)") { f =>
testRevokedTxConfirmed(f, ChannelFeatures())
testRevokedTxConfirmed(f, ChannelFeatures(Features.StaticRemoteKey))
}
test("recv WatchTxConfirmedTriggered (one revoked tx, pending htlcs, anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f =>

View File

@ -90,7 +90,8 @@ abstract class IntegrationSpec extends TestKitBaseClass with BitcoindService wit
"eclair.auto-reconnect" -> false,
"eclair.multi-part-payment-expiry" -> "20 seconds",
"eclair.channel.channel-update.balance-thresholds" -> Nil.asJava,
"eclair.channel.channel-update.min-time-between-updates" -> java.time.Duration.ZERO).asJava).withFallback(ConfigFactory.load())
"eclair.channel.channel-update.min-time-between-updates" -> java.time.Duration.ZERO,
"eclair.channel.accept-incoming-static-remote-key-channels" -> true).asJava).withFallback(ConfigFactory.load())
private val commonFeatures = ConfigFactory.parseMap(Map(
s"eclair.features.${DataLossProtect.rfcName}" -> "optional",

View File

@ -20,10 +20,11 @@ import akka.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe}
import akka.actor.typed.ActorRef
import akka.actor.typed.eventstream.EventStream
import akka.actor.typed.scaladsl.adapter.TypedActorRefOps
import com.softwaremill.quicklens.ModifyPimp
import com.typesafe.config.ConfigFactory
import fr.acinq.bitcoin.scalacompat.{ByteVector32, Crypto, SatoshiLong}
import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional}
import fr.acinq.eclair.Features.{AnchorOutputs, ChannelType, StaticRemoteKey, Wumbo}
import fr.acinq.eclair.Features.{AnchorOutputs, AnchorOutputsZeroFeeHtlcTx, ChannelType, StaticRemoteKey, Wumbo}
import fr.acinq.eclair.blockchain.DummyOnChainWallet
import fr.acinq.eclair.channel.ChannelTypes.UnsupportedChannelType
import fr.acinq.eclair.channel.fsm.Channel
@ -33,9 +34,8 @@ import fr.acinq.eclair.io.OpenChannelInterceptor.{DefaultParams, OpenChannelInit
import fr.acinq.eclair.io.Peer.{OpenChannelResponse, OutgoingMessage, SpawnChannelNonInitiator}
import fr.acinq.eclair.io.PeerSpec.createOpenChannelMessage
import fr.acinq.eclair.io.PendingChannelsRateLimiter.AddOrRejectChannel
import fr.acinq.eclair.payment.Bolt11Invoice.defaultFeatures.initFeatures
import fr.acinq.eclair.wire.protocol.{ChannelTlv, Error, IPAddress, NodeAddress, OpenChannel, OpenChannelTlv, TlvStream}
import fr.acinq.eclair.{AcceptOpenChannel, CltvExpiryDelta, Features, InterceptOpenChannelCommand, InterceptOpenChannelPlugin, InterceptOpenChannelReceived, MilliSatoshiLong, RejectOpenChannel, TestConstants, UnknownFeature, randomBytes32, randomKey}
import fr.acinq.eclair.{AcceptOpenChannel, CltvExpiryDelta, FeatureSupport, Features, InitFeature, InterceptOpenChannelCommand, InterceptOpenChannelPlugin, InterceptOpenChannelReceived, MilliSatoshiLong, RejectOpenChannel, TestConstants, UnknownFeature, randomBytes32, randomKey}
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
import org.scalatest.{Outcome, Tag}
@ -47,6 +47,9 @@ class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory
val defaultParams: DefaultParams = DefaultParams(100 sat, 100000 msat, 100 msat, CltvExpiryDelta(288), 10)
val openChannel: OpenChannel = createOpenChannelMessage()
val remoteAddress: NodeAddress = IPAddress(InetAddress.getLoopbackAddress, 19735)
val acceptStaticRemoteKeyChannelsTag = "accept static_remote_key channels"
val defaultFeatures: Features[InitFeature] = Features(Map[InitFeature, FeatureSupport](StaticRemoteKey -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional))
val staticRemoteKeyFeatures: Features[InitFeature] = Features(Map[InitFeature, FeatureSupport](StaticRemoteKey -> Optional))
override def withFixture(test: OneArgTest): Outcome = {
val peer = TestProbe[Any]()
@ -61,6 +64,8 @@ class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory
}
val pluginParams = TestConstants.Alice.nodeParams.pluginParams :+ plugin
val nodeParams = TestConstants.Alice.nodeParams.copy(pluginParams = pluginParams)
.modify(_.channelConf).usingIf(test.tags.contains(acceptStaticRemoteKeyChannelsTag))(_.copy(acceptIncomingStaticRemoteKeyChannels = true))
val eventListener = TestProbe[ChannelAborted]()
system.eventStream ! EventStream.Subscribe(eventListener.ref)
@ -73,7 +78,7 @@ class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory
test("reject channel open if timeout waiting for plugin to respond") { f =>
import f._
val openChannelNonInitiator = OpenChannelNonInitiator(remoteNodeId, Left(openChannel), Features.empty, Features.empty, peerConnection.ref, remoteAddress)
val openChannelNonInitiator = OpenChannelNonInitiator(remoteNodeId, Left(openChannel), defaultFeatures, defaultFeatures, peerConnection.ref, remoteAddress)
openChannelInterceptor ! openChannelNonInitiator
pendingChannelsRateLimiter.expectMessageType[AddOrRejectChannel].replyTo ! PendingChannelsRateLimiter.AcceptOpenChannel
pluginInterceptor.expectMessageType[InterceptOpenChannelReceived]
@ -84,7 +89,7 @@ class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory
test("continue channel open if pending channels rate limiter and interceptor plugin accept it") { f =>
import f._
val openChannelNonInitiator = OpenChannelNonInitiator(remoteNodeId, Left(openChannel), Features.empty, Features.empty, peerConnection.ref, remoteAddress)
val openChannelNonInitiator = OpenChannelNonInitiator(remoteNodeId, Left(openChannel), defaultFeatures, defaultFeatures, peerConnection.ref, remoteAddress)
openChannelInterceptor ! openChannelNonInitiator
pendingChannelsRateLimiter.expectMessageType[AddOrRejectChannel].replyTo ! PendingChannelsRateLimiter.AcceptOpenChannel
pluginInterceptor.expectMessageType[InterceptOpenChannelReceived].replyTo ! AcceptOpenChannel(randomBytes32(), defaultParams, Some(50_000 sat))
@ -103,18 +108,27 @@ class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory
// no open channel interceptor plugin registered
val wallet = new DummyOnChainWallet()
val openChannelInterceptor = testKit.spawn(OpenChannelInterceptor(peer.ref, TestConstants.Alice.nodeParams, remoteNodeId, wallet, pendingChannelsRateLimiter.ref, 10 millis))
val openChannelNonInitiator = OpenChannelNonInitiator(remoteNodeId, Left(openChannel), Features.empty, Features.empty, peerConnection.ref, remoteAddress)
val openChannelNonInitiator = OpenChannelNonInitiator(remoteNodeId, Left(openChannel), defaultFeatures, defaultFeatures, peerConnection.ref, remoteAddress)
openChannelInterceptor ! openChannelNonInitiator
pendingChannelsRateLimiter.expectMessageType[AddOrRejectChannel].replyTo ! PendingChannelsRateLimiter.AcceptOpenChannel
pluginInterceptor.expectNoMessage(10 millis)
peer.expectMessageType[SpawnChannelNonInitiator]
}
test("reject open channel request if rejected by the plugin") { f =>
test("reject open channel request if channel type is obsolete") { f =>
import f._
val openChannelNonInitiator = OpenChannelNonInitiator(remoteNodeId, Left(openChannel), Features.empty, Features.empty, peerConnection.ref, remoteAddress)
openChannelInterceptor ! openChannelNonInitiator
assert(peer.expectMessageType[OutgoingMessage].msg.asInstanceOf[Error].toAscii.contains("rejecting incoming channel: anchor outputs must be used for new channels"))
eventListener.expectMessageType[ChannelAborted]
}
test("reject open channel request if rejected by the plugin") { f =>
import f._
val openChannelNonInitiator = OpenChannelNonInitiator(remoteNodeId, Left(openChannel), defaultFeatures, defaultFeatures, peerConnection.ref, remoteAddress)
openChannelInterceptor ! openChannelNonInitiator
pendingChannelsRateLimiter.expectMessageType[AddOrRejectChannel].replyTo ! PendingChannelsRateLimiter.AcceptOpenChannel
pluginInterceptor.expectMessageType[InterceptOpenChannelReceived].replyTo ! RejectOpenChannel(randomBytes32(), Error(randomBytes32(), "rejected"))
assert(peer.expectMessageType[OutgoingMessage].msg.asInstanceOf[Error].toAscii.contains("rejected"))
@ -124,7 +138,7 @@ class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory
test("reject open channel request if pending channels rate limit reached") { f =>
import f._
val openChannelNonInitiator = OpenChannelNonInitiator(remoteNodeId, Left(openChannel), Features.empty, Features.empty, peerConnection.ref, remoteAddress)
val openChannelNonInitiator = OpenChannelNonInitiator(remoteNodeId, Left(openChannel), defaultFeatures, defaultFeatures, peerConnection.ref, remoteAddress)
openChannelInterceptor ! openChannelNonInitiator
pendingChannelsRateLimiter.expectMessageType[AddOrRejectChannel].replyTo ! PendingChannelsRateLimiter.ChannelRateLimited
assert(peer.expectMessageType[OutgoingMessage].msg.asInstanceOf[Error].toAscii.contains("rate limit reached"))
@ -134,7 +148,7 @@ class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory
test("reject open channel request if concurrent request in progress") { f =>
import f._
val openChannelNonInitiator = OpenChannelNonInitiator(remoteNodeId, Left(openChannel), Features.empty, Features.empty, peerConnection.ref, remoteAddress)
val openChannelNonInitiator = OpenChannelNonInitiator(remoteNodeId, Left(openChannel), defaultFeatures, defaultFeatures, peerConnection.ref, remoteAddress)
openChannelInterceptor ! openChannelNonInitiator
// waiting for rate limiter to respond to the first request, do not accept any other requests
@ -152,12 +166,31 @@ class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory
eventListener.expectMessageType[ChannelAborted]
}
test("reject static_remote_key open channel request") { f =>
import f._
val openChannelNonInitiator = OpenChannelNonInitiator(remoteNodeId, Left(openChannel), staticRemoteKeyFeatures, staticRemoteKeyFeatures, peerConnection.ref, remoteAddress)
openChannelInterceptor ! openChannelNonInitiator
assert(peer.expectMessageType[OutgoingMessage].msg.asInstanceOf[Error].toAscii.contains("rejecting incoming static_remote_key channel: anchor outputs must be used for new channels"))
eventListener.expectMessageType[ChannelAborted]
}
test("accept static_remote_key open channel request if node is configured to accept them", Tag(acceptStaticRemoteKeyChannelsTag)) { f =>
import f._
val openChannelNonInitiator = OpenChannelNonInitiator(remoteNodeId, Left(openChannel), staticRemoteKeyFeatures, staticRemoteKeyFeatures, peerConnection.ref, remoteAddress)
openChannelInterceptor ! openChannelNonInitiator
pendingChannelsRateLimiter.expectMessageType[AddOrRejectChannel].replyTo ! PendingChannelsRateLimiter.AcceptOpenChannel
pluginInterceptor.expectMessageType[InterceptOpenChannelReceived].replyTo ! AcceptOpenChannel(randomBytes32(), defaultParams, Some(50_000 sat))
peer.expectMessageType[SpawnChannelNonInitiator]
}
test("don't spawn a wumbo channel if wumbo feature isn't enabled", Tag(ChannelStateTestsTags.DisableWumbo)) { f =>
import f._
val probe = TestProbe[Any]()
val fundingAmountBig = Channel.MAX_FUNDING_WITHOUT_WUMBO + 10_000.sat
openChannelInterceptor ! OpenChannelInitiator(probe.ref, remoteNodeId, Peer.OpenChannel(remoteNodeId, fundingAmountBig, None, None, None, None, None, None), Features.empty, initFeatures().add(Wumbo, Optional))
openChannelInterceptor ! OpenChannelInitiator(probe.ref, remoteNodeId, Peer.OpenChannel(remoteNodeId, fundingAmountBig, None, None, None, None, None, None), defaultFeatures, defaultFeatures.add(Wumbo, Optional))
assert(probe.expectMessageType[OpenChannelResponse.Rejected].reason.contains("you must enable large channels support"))
}
@ -166,7 +199,7 @@ class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory
val probe = TestProbe[Any]()
val fundingAmountBig = Channel.MAX_FUNDING_WITHOUT_WUMBO + 10_000.sat
openChannelInterceptor ! OpenChannelInitiator(probe.ref, remoteNodeId, Peer.OpenChannel(remoteNodeId, fundingAmountBig, None, None, None, None, None, None), initFeatures().add(Wumbo, Optional), Features.empty)
openChannelInterceptor ! OpenChannelInitiator(probe.ref, remoteNodeId, Peer.OpenChannel(remoteNodeId, fundingAmountBig, None, None, None, None, None, None), defaultFeatures.add(Wumbo, Optional), defaultFeatures)
assert(probe.expectMessageType[OpenChannelResponse.Rejected].reason == s"fundingAmount=$fundingAmountBig is too big, the remote peer doesn't support wumbo")
}
@ -207,7 +240,7 @@ class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory
import f._
val open = createOpenChannelMessage()
openChannelInterceptor ! OpenChannelNonInitiator(remoteNodeId, Left(open), initFeatures().add(ChannelType, Optional), initFeatures().add(ChannelType, Optional), peerConnection.ref, remoteAddress)
openChannelInterceptor ! OpenChannelNonInitiator(remoteNodeId, Left(open), defaultFeatures.add(ChannelType, Optional), defaultFeatures.add(ChannelType, Optional), peerConnection.ref, remoteAddress)
peer.expectMessage(OutgoingMessage(Error(open.temporaryChannelId, "option_channel_type was negotiated but channel_type is missing"), peerConnection.ref.toClassic))
eventListener.expectMessageType[ChannelAborted]
}

View File

@ -21,7 +21,7 @@ import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32}
import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional}
import fr.acinq.eclair.Features.{BasicMultiPartPayment, ChannelRangeQueries, PaymentSecret, VariableLengthOnion}
import fr.acinq.eclair.Features.{BasicMultiPartPayment, ChannelRangeQueries, PaymentSecret, StaticRemoteKey, VariableLengthOnion}
import fr.acinq.eclair.TestConstants._
import fr.acinq.eclair.crypto.TransportHandler
import fr.acinq.eclair.io.Peer.ConnectionDown
@ -153,7 +153,7 @@ class PeerConnectionSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wi
transport.send(peerConnection, LightningMessageCodecs.initCodec.decode(hex"0000 00050100000000".bits).require.value)
transport.expectMsgType[TransportHandler.ReadAck]
probe.expectTerminated(transport.ref)
origin.expectMsg(PeerConnection.ConnectionResult.InitializationFailed("incompatible features (unknown_32,payment_secret,var_onion_optin)"))
origin.expectMsg(PeerConnection.ConnectionResult.InitializationFailed("incompatible features (unknown_32,payment_secret,var_onion_optin,option_static_remotekey)"))
peer.expectMsg(ConnectionDown(peerConnection))
}
@ -170,7 +170,7 @@ class PeerConnectionSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wi
transport.send(peerConnection, LightningMessageCodecs.initCodec.decode(hex"00050100000000 0000".bits).require.value)
transport.expectMsgType[TransportHandler.ReadAck]
probe.expectTerminated(transport.ref)
origin.expectMsg(PeerConnection.ConnectionResult.InitializationFailed("incompatible features (unknown_32,payment_secret,var_onion_optin)"))
origin.expectMsg(PeerConnection.ConnectionResult.InitializationFailed("incompatible features (unknown_32,payment_secret,var_onion_optin,option_static_remotekey)"))
peer.expectMsg(ConnectionDown(peerConnection))
}
@ -211,7 +211,7 @@ class PeerConnectionSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wi
test("sync when requested") { f =>
import f._
val remoteInit = protocol.Init(Features(ChannelRangeQueries -> Optional, VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory))
val remoteInit = protocol.Init(Features(ChannelRangeQueries -> Optional, VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory, StaticRemoteKey -> Mandatory))
connect(nodeParams, remoteNodeId, switchboard, router, connection, transport, peerConnection, peer, remoteInit, doSync = true)
}

View File

@ -325,7 +325,7 @@ class PeerSpec extends FixtureSpec {
monitor.expectMsg(FSM.Transition(reconnectionTask, ReconnectionTask.CONNECTING, ReconnectionTask.IDLE))
}
test("don't spawn a channel with duplicate temporary channel id") { f =>
test("don't spawn a channel with duplicate temporary channel id", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
val probe = TestProbe()
@ -403,19 +403,19 @@ class PeerSpec extends FixtureSpec {
channel.expectMsg(open)
}
test("use their channel type when spawning a channel", Tag(ChannelStateTestsTags.StaticRemoteKey)) { f =>
test("use their channel type when spawning a channel", Tag(ChannelStateTestsTags.AnchorOutputs)) { f =>
import f._
// We both support option_static_remotekey but they want to open a standard channel.
connect(remoteNodeId, peer, peerConnection, switchboard, remoteInit = protocol.Init(Features(StaticRemoteKey -> Optional)))
// We both support option_anchors_zero_fee_htlc_tx they want to open an anchor_outputs channel.
connect(remoteNodeId, peer, peerConnection, switchboard, remoteInit = protocol.Init(Features(AnchorOutputsZeroFeeHtlcTx -> Optional)))
assert(peer.stateData.channels.isEmpty)
val open = createOpenChannelMessage(TlvStream[OpenChannelTlv](ChannelTlv.ChannelTypeTlv(ChannelTypes.Standard())))
val open = createOpenChannelMessage(TlvStream[OpenChannelTlv](ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputs())))
peerConnection.send(peer, open)
eventually {
assert(peer.stateData.channels.nonEmpty)
}
val init = channel.expectMsgType[INPUT_INIT_CHANNEL_NON_INITIATOR]
assert(init.channelType == ChannelTypes.Standard())
assert(init.channelType == ChannelTypes.AnchorOutputs())
assert(!init.dualFunded)
channel.expectMsg(open)
}
@ -439,7 +439,7 @@ class PeerSpec extends FixtureSpec {
assert(channel.expectMsgType[INPUT_INIT_CHANNEL_INITIATOR].channelType == ChannelTypes.AnchorOutputs())
}
test("handle OpenChannelInterceptor accepting an open channel message") { f =>
test("handle OpenChannelInterceptor accepting an open channel message", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
connect(remoteNodeId, peer, peerConnection, switchboard)
@ -450,7 +450,7 @@ class PeerSpec extends FixtureSpec {
channel.expectMsg(open)
}
test("handle OpenChannelInterceptor rejecting an open channel message", Tag("rate_limited")) { f =>
test("handle OpenChannelInterceptor rejecting an open channel message", Tag("rate_limited"), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
connect(remoteNodeId, peer, peerConnection, switchboard)
@ -510,7 +510,7 @@ class PeerSpec extends FixtureSpec {
assert(init.localParams.upfrontShutdownScript_opt.isEmpty)
}
test("compute max-htlc-value-in-flight based on funding amount", Tag("max-htlc-value-in-flight-percent")) { f =>
test("compute max-htlc-value-in-flight based on funding amount", Tag("max-htlc-value-in-flight-percent"), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
val probe = TestProbe()