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:
parent
f8d6acb326
commit
86373b4411
@ -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>
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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")
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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),
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 */
|
||||
|
@ -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)
|
||||
|
@ -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]
|
||||
|
@ -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)
|
||||
|
@ -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]
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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 =>
|
||||
|
@ -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",
|
||||
|
@ -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]
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user