From 8d4da2faadb5966710e83e33736a946b1df93b9a Mon Sep 17 00:00:00 2001 From: Pierre-Marie Padiou Date: Fri, 26 Feb 2021 18:24:45 +0100 Subject: [PATCH] Improve channel state tests (#1709) * add tests on funding mindepth We verify that when using wumbo channels: - if we are funder we keep our regular min_depth - if we are fundee we use a greater min_depth * use lenses to simplify tags handling Co-authored-by: Bastien Teinturier <31281497+t-bast@users.noreply.github.com> --- .../states/StateTestsHelperMethods.scala | 34 ++++++++----- .../b/WaitForFundingCreatedStateSpec.scala | 47 +++++++++++++---- .../b/WaitForFundingSignedStateSpec.scala | 50 +++++++++++++++---- 3 files changed, 100 insertions(+), 31 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala index a370a38f0..40a90cd02 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala @@ -98,29 +98,37 @@ trait StateTestsHelperMethods extends TestKitBase { } def reachNormal(setup: SetupFixture, tags: Set[String] = Set.empty): Unit = { + import com.softwaremill.quicklens._ import setup._ + val channelVersion = List( + ChannelVersion.STANDARD, + if (tags.contains(StateTestsTags.AnchorOutputs)) ChannelVersion.ANCHOR_OUTPUTS else ChannelVersion.ZEROES, + if (tags.contains(StateTestsTags.StaticRemoteKey)) ChannelVersion.STATIC_REMOTEKEY else ChannelVersion.ZEROES, + ).reduce(_ | _) + val channelFlags = if (tags.contains(StateTestsTags.ChannelsPublic)) ChannelFlags.AnnounceChannel else ChannelFlags.Empty - val pushMsat = if (tags.contains(StateTestsTags.NoPushMsat)) 0.msat else TestConstants.pushMsat - val (aliceParams, bobParams, channelVersion) = if (tags.contains(StateTestsTags.AnchorOutputs)) { - val features = Features(Set(ActivatedFeature(Features.StaticRemoteKey, FeatureSupport.Mandatory), ActivatedFeature(Features.AnchorOutputs, FeatureSupport.Optional))) - (Alice.channelParams.copy(features = features), Bob.channelParams.copy(features = features), ChannelVersion.ANCHOR_OUTPUTS) - } else if (tags.contains(StateTestsTags.StaticRemoteKey)) { - val features = Features(Set(ActivatedFeature(Features.StaticRemoteKey, FeatureSupport.Optional))) - val aliceParams = Alice.channelParams.copy(features = features, walletStaticPaymentBasepoint = Some(Helpers.getWalletPaymentBasepoint(wallet))) - val bobParams = Bob.channelParams.copy(features = features, walletStaticPaymentBasepoint = Some(Helpers.getWalletPaymentBasepoint(wallet))) - (aliceParams, bobParams, ChannelVersion.STATIC_REMOTEKEY) - } else { - (Alice.channelParams, Bob.channelParams, ChannelVersion.STANDARD) - } + val aliceParams = Alice.channelParams + .modify(_.features.activated).usingIf(channelVersion.hasStaticRemotekey)(_ ++ Set(ActivatedFeature(Features.StaticRemoteKey, FeatureSupport.Optional))) + .modify(_.features.activated).usingIf(channelVersion.hasAnchorOutputs)(_ ++ Set(ActivatedFeature(Features.StaticRemoteKey, FeatureSupport.Mandatory), ActivatedFeature(Features.AnchorOutputs, FeatureSupport.Optional))) + .modify(_.walletStaticPaymentBasepoint).setToIf(channelVersion.paysDirectlyToWallet)(Some(Helpers.getWalletPaymentBasepoint(wallet))) + val bobParams = Bob.channelParams + .modify(_.features.activated).usingIf(channelVersion.hasStaticRemotekey)(_ ++ Set(ActivatedFeature(Features.StaticRemoteKey, FeatureSupport.Optional))) + .modify(_.features.activated).usingIf(channelVersion.hasAnchorOutputs)(_ ++ Set(ActivatedFeature(Features.StaticRemoteKey, FeatureSupport.Mandatory), ActivatedFeature(Features.AnchorOutputs, FeatureSupport.Optional))) + .modify(_.walletStaticPaymentBasepoint).setToIf(channelVersion.paysDirectlyToWallet)(Some(Helpers.getWalletPaymentBasepoint(wallet))) val initialFeeratePerKw = if (tags.contains(StateTestsTags.AnchorOutputs)) { FeeEstimator.AnchorOutputMaxCommitFeerate } else { TestConstants.feeratePerKw } + val (fundingSatoshis, pushMsat) = if (tags.contains(StateTestsTags.NoPushMsat)) { + (TestConstants.fundingSatoshis, 0.msat) + } else { + (TestConstants.fundingSatoshis, TestConstants.pushMsat) + } val aliceInit = Init(aliceParams.features) val bobInit = Init(bobParams.features) - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, pushMsat, initialFeeratePerKw, TestConstants.feeratePerKw, None, aliceParams, alice2bob.ref, bobInit, channelFlags, channelVersion) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, fundingSatoshis, pushMsat, initialFeeratePerKw, TestConstants.feeratePerKw, None, aliceParams, alice2bob.ref, bobInit, channelFlags, channelVersion) bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, aliceInit, channelVersion) alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala index e941c1286..c796acc49 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala @@ -18,14 +18,16 @@ package fr.acinq.eclair.channel.states.b import akka.actor.ActorRef import akka.testkit.{TestFSMRef, TestProbe} -import fr.acinq.bitcoin.{ByteVector32, SatoshiLong} +import fr.acinq.bitcoin.{Btc, ByteVector32, SatoshiLong} +import fr.acinq.eclair.FeatureSupport.Optional +import fr.acinq.eclair.Features.Wumbo import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.states.StateTestsBase import fr.acinq.eclair.transactions.Transactions import fr.acinq.eclair.wire._ -import fr.acinq.eclair.{TestConstants, TestKitBaseClass, ToMilliSatoshiConversion} +import fr.acinq.eclair.{ActivatedFeature, Features, TestConstants, TestKitBaseClass, ToMilliSatoshiConversion} import org.scalatest.funsuite.FixtureAnyFunSuiteLike import org.scalatest.{Outcome, Tag} @@ -40,18 +42,32 @@ class WaitForFundingCreatedStateSpec extends TestKitBaseClass with FixtureAnyFun case class FixtureParam(bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, bob2blockchain: TestProbe) override def withFixture(test: OneArgTest): Outcome = { - val setup = init() - import setup._ + import com.softwaremill.quicklens._ + val aliceNodeParams = Alice.nodeParams + .modify(_.maxFundingSatoshis).setToIf(test.tags.contains("wumbo"))(Btc(100)) + val aliceParams = Alice.channelParams + .modify(_.features).setToIf(test.tags.contains("wumbo"))(Features(Set(ActivatedFeature(Wumbo, Optional)))) + val bobNodeParams = Bob.nodeParams + .modify(_.maxFundingSatoshis).setToIf(test.tags.contains("wumbo"))(Btc(100)) + val bobParams = Bob.channelParams + .modify(_.features).setToIf(test.tags.contains("wumbo"))(Features(Set(ActivatedFeature(Wumbo, Optional)))) + val (fundingSatoshis, pushMsat) = if (test.tags.contains("funder_below_reserve")) { (1000100 sat, (1000000 sat).toMilliSatoshi) // toLocal = 100 satoshis + } else if (test.tags.contains("wumbo")) { + (Btc(5).toSatoshi, TestConstants.pushMsat) } else { (TestConstants.fundingSatoshis, TestConstants.pushMsat) } - val aliceInit = Init(Alice.channelParams.features) - val bobInit = Init(Bob.channelParams.features) + + val setup = init(aliceNodeParams, bobNodeParams) + + import setup._ + val aliceInit = Init(aliceParams.features) + val bobInit = Init(bobParams.features) within(30 seconds) { - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, fundingSatoshis, pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, None, Alice.channelParams, alice2bob.ref, bobInit, ChannelFlags.Empty, ChannelVersion.STANDARD) - bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit, ChannelVersion.STANDARD) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, fundingSatoshis, pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, None, aliceParams, alice2bob.ref, bobInit, ChannelFlags.Empty, ChannelVersion.STANDARD) + bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, aliceInit, ChannelVersion.STANDARD) alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) bob2alice.expectMsgType[AcceptChannel] @@ -68,7 +84,20 @@ class WaitForFundingCreatedStateSpec extends TestKitBaseClass with FixtureAnyFun awaitCond(bob.stateName == WAIT_FOR_FUNDING_CONFIRMED) bob2alice.expectMsgType[FundingSigned] bob2blockchain.expectMsgType[WatchSpent] - bob2blockchain.expectMsgType[WatchConfirmed] + val watchConfirmed = bob2blockchain.expectMsgType[WatchConfirmed] + assert(watchConfirmed.minDepth === Alice.nodeParams.minDepthBlocks) + } + + test("recv FundingCreated (wumbo)", Tag("wumbo")) { f => + import f._ + alice2bob.expectMsgType[FundingCreated] + alice2bob.forward(bob) + awaitCond(bob.stateName == WAIT_FOR_FUNDING_CONFIRMED) + bob2alice.expectMsgType[FundingSigned] + bob2blockchain.expectMsgType[WatchSpent] + val watchConfirmed = bob2blockchain.expectMsgType[WatchConfirmed] + // when we are fundee, we use a higher min depth for wumbo channels + assert(watchConfirmed.minDepth > Bob.nodeParams.minDepthBlocks) } test("recv FundingCreated (funder can't pay fees)", Tag("funder_below_reserve")) { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala index 8ff55d74b..b48bd77b7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala @@ -17,15 +17,17 @@ package fr.acinq.eclair.channel.states.b import akka.testkit.{TestFSMRef, TestProbe} -import fr.acinq.bitcoin.{ByteVector32, ByteVector64} +import fr.acinq.bitcoin.{Btc, ByteVector32, ByteVector64} +import fr.acinq.eclair.FeatureSupport.Optional +import fr.acinq.eclair.Features.Wumbo import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.channel.Channel.TickChannelOpenTimeout import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.states.StateTestsBase import fr.acinq.eclair.wire.{AcceptChannel, Error, FundingCreated, FundingSigned, Init, OpenChannel} -import fr.acinq.eclair.{TestConstants, TestKitBaseClass} -import org.scalatest.Outcome +import fr.acinq.eclair.{ActivatedFeature, Features, TestConstants, TestKitBaseClass} +import org.scalatest.{Outcome, Tag} import org.scalatest.funsuite.FixtureAnyFunSuiteLike import scala.concurrent.duration._ @@ -39,13 +41,30 @@ class WaitForFundingSignedStateSpec extends TestKitBaseClass with FixtureAnyFunS case class FixtureParam(alice: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe) override def withFixture(test: OneArgTest): Outcome = { - val setup = init() + import com.softwaremill.quicklens._ + val aliceNodeParams = Alice.nodeParams + .modify(_.maxFundingSatoshis).setToIf(test.tags.contains("wumbo"))(Btc(100)) + val aliceParams = Alice.channelParams + .modify(_.features).setToIf(test.tags.contains("wumbo"))(Features(Set(ActivatedFeature(Wumbo, Optional)))) + val bobNodeParams = Bob.nodeParams + .modify(_.maxFundingSatoshis).setToIf(test.tags.contains("wumbo"))(Btc(100)) + val bobParams = Bob.channelParams + .modify(_.features).setToIf(test.tags.contains("wumbo"))(Features(Set(ActivatedFeature(Wumbo, Optional)))) + + val (fundingSatoshis, pushMsat) = if (test.tags.contains("wumbo")) { + (Btc(5).toSatoshi, TestConstants.pushMsat) + } else { + (TestConstants.fundingSatoshis, TestConstants.pushMsat) + } + + val setup = init(aliceNodeParams, bobNodeParams) + import setup._ - val aliceInit = Init(Alice.channelParams.features) - val bobInit = Init(Bob.channelParams.features) + val aliceInit = Init(aliceParams.features) + val bobInit = Init(bobParams.features) within(30 seconds) { - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, None, Alice.channelParams, alice2bob.ref, bobInit, ChannelFlags.Empty, ChannelVersion.STANDARD) - bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit, ChannelVersion.STANDARD) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, fundingSatoshis, pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, None, aliceParams, alice2bob.ref, bobInit, ChannelFlags.Empty, ChannelVersion.STANDARD) + bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, aliceInit, ChannelVersion.STANDARD) alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) bob2alice.expectMsgType[AcceptChannel] @@ -63,7 +82,20 @@ class WaitForFundingSignedStateSpec extends TestKitBaseClass with FixtureAnyFunS bob2alice.forward(alice) awaitCond(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED) alice2blockchain.expectMsgType[WatchSpent] - alice2blockchain.expectMsgType[WatchConfirmed] + val watchConfirmed = alice2blockchain.expectMsgType[WatchConfirmed] + // when we are funder, we keep our regular min depth even for wumbo channels + assert(watchConfirmed.minDepth === Alice.nodeParams.minDepthBlocks) + } + + test("recv FundingSigned with valid signature (wumbo)", Tag("wumbo")) { f => + import f._ + bob2alice.expectMsgType[FundingSigned] + bob2alice.forward(alice) + awaitCond(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED) + alice2blockchain.expectMsgType[WatchSpent] + val watchConfirmed = alice2blockchain.expectMsgType[WatchConfirmed] + + assert(watchConfirmed.minDepth === Alice.nodeParams.minDepthBlocks) } test("recv FundingSigned with invalid signature") { f =>