1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-02-23 06:35:11 +01:00

Activate 0-conf based on per-peer feature override (#2329)

We restrict 0-conf activation on a per-peer basis. When 0-conf is activated
by both peers, we use it even if it wasn't part of the `channel_type`.
This commit is contained in:
Bastien Teinturier 2022-07-01 15:13:57 +02:00 committed by GitHub
parent a1f7c1e74f
commit 2790b2ff6c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 112 additions and 45 deletions

View file

@ -21,7 +21,7 @@ This feature is enabled by default, but your peer has to support it too, and it
Zeroconf channels make it possible to use a newly created channel before the funding tx is confirmed on the blockchain.
:warning: Zeroconf requires the fundee to trust the funder. For this reason it is disabled by default, and you should
:warning: Zeroconf requires the fundee to trust the funder. For this reason it is disabled by default, and you can
only enable it on a peer-by-peer basis.
##### Enabling through features

View file

@ -63,8 +63,9 @@ eclair {
option_channel_type = optional
option_scid_alias = optional
option_payment_metadata = optional
// By enabling option_zeroconf, you will be trusting your peers as fundee. You will lose funds if they double spend
// their funding tx.
// By enabling option_zeroconf, you will be trusting your peer as fundee. You will lose funds if they double spend
// their funding tx. Eclair does not let you activate this feature by default, you have to activate it for every
// node that you trust using override-init-features (see below).
option_zeroconf = disabled
keysend = disabled
trampoline_payment_prototype = disabled

View file

@ -302,6 +302,7 @@ object NodeParams extends Logging {
val pluginMessageParams = pluginParams.collect { case p: CustomFeaturePlugin => p }
val features = Features.fromConfiguration(config.getConfig("features"))
validateFeatures(features)
require(!features.hasFeature(Features.ZeroConf), s"${Features.ZeroConf.rfcName} cannot be enabled for all peers: you have to use override-init-features to enable it on a per-peer basis")
require(pluginMessageParams.forall(_.feature.mandatory > 128), "Plugin mandatory feature bit is too low, must be > 128")
require(pluginMessageParams.forall(_.feature.mandatory % 2 == 0), "Plugin mandatory feature bit is odd, must be even")

View file

@ -17,7 +17,6 @@
package fr.acinq.eclair.channel
import fr.acinq.eclair.transactions.Transactions.{CommitmentFormat, DefaultCommitmentFormat, UnsafeLegacyAnchorOutputsCommitmentFormat, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat}
import fr.acinq.eclair.{Feature, FeatureSupport, Features, InitFeature}
import fr.acinq.eclair.{ChannelTypeFeature, FeatureSupport, Features, InitFeature, PermanentChannelFeature}
/**
@ -57,10 +56,15 @@ object ChannelFeatures {
def apply(features: PermanentChannelFeature*): ChannelFeatures = ChannelFeatures(Set.from(features))
/** Enrich the channel type with other permanent features that will be applied to the channel. */
def apply(channelType: SupportedChannelType, localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature]): ChannelFeatures = {
def apply(channelType: SupportedChannelType, localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature], announceChannel: Boolean): ChannelFeatures = {
val additionalPermanentFeatures = Features.knownFeatures.collect {
case _: ChannelTypeFeature => None // channel-type features are negotiated in the channel-type, we ignore them in the init
case f: PermanentChannelFeature if Features.canUseFeature(localFeatures, remoteFeatures, f) => Some(f) // we only consider permanent channel features
// If we both support 0-conf or scid_alias, we use it even if it wasn't in the channel-type.
case Features.ScidAlias if Features.canUseFeature(localFeatures, remoteFeatures, Features.ScidAlias) && !announceChannel => Some(Features.ScidAlias)
case Features.ZeroConf if Features.canUseFeature(localFeatures, remoteFeatures, Features.ZeroConf) => Some(Features.ZeroConf)
// Other channel-type features are negotiated in the channel-type, we ignore their value from the init message.
case _: ChannelTypeFeature => None
// We add all other permanent channel features that aren't negotiated as part of the channel-type.
case f: PermanentChannelFeature if Features.canUseFeature(localFeatures, remoteFeatures, f) => Some(f)
}.flatten
val allPermanentFeatures = channelType.features.toSeq ++ additionalPermanentFeatures
ChannelFeatures(allPermanentFeatures: _*)
@ -139,7 +143,7 @@ object ChannelTypes {
/** Pick the channel type based on local and remote feature bits, as defined by the spec. */
def defaultFromFeatures(localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature], announceChannel: Boolean): SupportedChannelType = {
def canUse(feature: InitFeature) = Features.canUseFeature(localFeatures, remoteFeatures, feature)
def canUse(feature: InitFeature): Boolean = Features.canUseFeature(localFeatures, remoteFeatures, feature)
if (canUse(Features.AnchorOutputsZeroFeeHtlcTx)) {
AnchorOutputsZeroFeeHtlcTx(scidAlias = canUse(Features.ScidAlias) && !announceChannel, zeroConf = canUse(Features.ZeroConf)) // alias feature is incompatible with public channel

View file

@ -131,7 +131,7 @@ object Helpers {
val reserveToFundingRatio = open.channelReserveSatoshis.toLong.toDouble / Math.max(open.fundingSatoshis.toLong, 1)
if (reserveToFundingRatio > nodeParams.channelConf.maxReserveToFundingRatio) return Left(ChannelReserveTooHigh(open.temporaryChannelId, open.channelReserveSatoshis, reserveToFundingRatio, nodeParams.channelConf.maxReserveToFundingRatio))
val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures)
val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel)
extractShutdownScript(open.temporaryChannelId, localFeatures, remoteFeatures, open.upfrontShutdownScript_opt).map(script_opt => (channelFeatures, script_opt))
}
@ -179,7 +179,7 @@ object Helpers {
val reserveToFundingRatio = accept.channelReserveSatoshis.toLong.toDouble / Math.max(open.fundingSatoshis.toLong, 1)
if (reserveToFundingRatio > nodeParams.channelConf.maxReserveToFundingRatio) return Left(ChannelReserveTooHigh(open.temporaryChannelId, accept.channelReserveSatoshis, reserveToFundingRatio, nodeParams.channelConf.maxReserveToFundingRatio))
val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures)
val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel)
extractShutdownScript(accept.temporaryChannelId, localFeatures, remoteFeatures, accept.upfrontShutdownScript_opt).map(script_opt => (channelFeatures, script_opt))
}

View file

@ -255,6 +255,35 @@ class StartupSpec extends AnyFunSuite {
assertThrows[IllegalArgumentException](makeNodeParamsWithDefaults(perNodeConf.withFallback(defaultConf)))
}
test("disallow enabling zero-conf for every peer") {
val invalidConf = ConfigFactory.parseString(
"""
| features {
| option_zeroconf = optional
| }
""".stripMargin
)
assertThrows[IllegalArgumentException](makeNodeParamsWithDefaults(invalidConf.withFallback(defaultConf)))
val perNodeConf = ConfigFactory.parseString(
"""
| override-init-features = [
| {
| nodeid = "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f"
| features {
| option_zeroconf = optional
| }
| }
| ]
""".stripMargin
)
val nodeParams = makeNodeParamsWithDefaults(perNodeConf.withFallback(defaultConf))
assert(!nodeParams.features.hasFeature(Features.ZeroConf))
val perNodeFeatures = nodeParams.initFeaturesFor(PublicKey(ByteVector.fromValidHex("031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")))
assert(perNodeFeatures.hasFeature(Features.ZeroConf))
}
test("override feerate mismatch tolerance") {
val perNodeConf = ConfigFactory.parseString(
"""

View file

@ -122,20 +122,24 @@ class ChannelFeaturesSpec extends TestKitBaseClass with AnyFunSuiteLike with Cha
}
test("enrich channel type with optional permanent channel features") {
case class TestCase(channelType: SupportedChannelType, localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature], expected: Set[Feature])
case class TestCase(channelType: SupportedChannelType, localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature], announceChannel: Boolean, expected: Set[Feature])
val testCases = Seq(
TestCase(ChannelTypes.Standard, Features(Wumbo -> Optional), Features.empty, Set.empty),
TestCase(ChannelTypes.Standard, Features(Wumbo -> Optional), Features(Wumbo -> Optional), Set(Wumbo)),
TestCase(ChannelTypes.Standard, Features(Wumbo -> Mandatory), Features(Wumbo -> Optional), Set(Wumbo)),
TestCase(ChannelTypes.StaticRemoteKey, Features(Wumbo -> Optional), Features.empty, Set(StaticRemoteKey)),
TestCase(ChannelTypes.StaticRemoteKey, Features(Wumbo -> Optional), Features(Wumbo -> Optional), Set(StaticRemoteKey, Wumbo)),
TestCase(ChannelTypes.AnchorOutputs, Features.empty, Features(Wumbo -> Optional), Set(StaticRemoteKey, AnchorOutputs)),
TestCase(ChannelTypes.AnchorOutputs, Features(Wumbo -> Optional), Features(Wumbo -> Mandatory), Set(StaticRemoteKey, AnchorOutputs, Wumbo)),
TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false), Features.empty, Features(Wumbo -> Optional), Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx)),
TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false), Features(Wumbo -> Optional), Features(Wumbo -> Mandatory), Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx, Wumbo)),
TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false), Features(DualFunding -> Optional, Wumbo -> Optional), Features(DualFunding -> Optional, Wumbo -> Optional), Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx, Wumbo, DualFunding)),
TestCase(ChannelTypes.Standard, Features(Wumbo -> Optional), Features.empty, announceChannel = true, Set.empty),
TestCase(ChannelTypes.Standard, Features(Wumbo -> Optional), Features(Wumbo -> Optional), announceChannel = true, Set(Wumbo)),
TestCase(ChannelTypes.Standard, Features(Wumbo -> Mandatory), Features(Wumbo -> Optional), announceChannel = true, Set(Wumbo)),
TestCase(ChannelTypes.StaticRemoteKey, Features(Wumbo -> Optional), Features.empty, announceChannel = true, Set(StaticRemoteKey)),
TestCase(ChannelTypes.StaticRemoteKey, Features(Wumbo -> Optional), Features(Wumbo -> Optional), announceChannel = true, Set(StaticRemoteKey, Wumbo)),
TestCase(ChannelTypes.AnchorOutputs, Features.empty, Features(Wumbo -> Optional), announceChannel = true, Set(StaticRemoteKey, AnchorOutputs)),
TestCase(ChannelTypes.AnchorOutputs, Features(Wumbo -> Optional), Features(Wumbo -> Mandatory), announceChannel = true, Set(StaticRemoteKey, AnchorOutputs, Wumbo)),
TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false), Features.empty, Features(Wumbo -> Optional), announceChannel = true, Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx)),
TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false), Features(ScidAlias -> Optional, ZeroConf -> Optional), Features(ScidAlias -> Optional, ZeroConf -> Optional), announceChannel = true, Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx, ZeroConf)),
TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false), Features(ScidAlias -> Optional, ZeroConf -> Optional), Features(ScidAlias -> Optional, ZeroConf -> Optional), announceChannel = false, Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx, ScidAlias, ZeroConf)),
TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = false), Features.empty, Features(Wumbo -> Optional), announceChannel = false, Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx, ScidAlias)),
TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true), Features.empty, Features(Wumbo -> Optional), announceChannel = false, Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx, ScidAlias, ZeroConf)),
TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false), Features(Wumbo -> Optional), Features(Wumbo -> Mandatory), announceChannel = true, Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx, Wumbo)),
TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false), Features(DualFunding -> Optional, Wumbo -> Optional), Features(DualFunding -> Optional, Wumbo -> Optional), announceChannel = true, Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx, Wumbo, DualFunding)),
)
testCases.foreach(t => assert(ChannelFeatures(t.channelType, t.localFeatures, t.remoteFeatures).features == t.expected, s"channelType=${t.channelType} localFeatures=${t.localFeatures} remoteFeatures=${t.remoteFeatures}"))
testCases.foreach(t => assert(ChannelFeatures(t.channelType, t.localFeatures, t.remoteFeatures, t.announceChannel).features == t.expected, s"channelType=${t.channelType} localFeatures=${t.localFeatures} remoteFeatures=${t.remoteFeatures}"))
}
}

View file

@ -77,8 +77,8 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS
import f._
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 == Some(ByteVector.empty))
assert(accept.channelType_opt == Some(ChannelTypes.Standard))
assert(accept.upfrontShutdownScript_opt.contains(ByteVector.empty))
assert(accept.channelType_opt.contains(ChannelTypes.Standard))
bob2alice.forward(alice)
awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL)
aliceOrigin.expectNoMessage()
@ -87,7 +87,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS
test("recv AcceptChannel (anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f =>
import f._
val accept = bob2alice.expectMsgType[AcceptChannel]
assert(accept.channelType_opt == Some(ChannelTypes.AnchorOutputs))
assert(accept.channelType_opt.contains(ChannelTypes.AnchorOutputs))
bob2alice.forward(alice)
awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL)
assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].channelFeatures.channelType == ChannelTypes.AnchorOutputs)
@ -97,7 +97,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS
test("recv AcceptChannel (anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
val accept = bob2alice.expectMsgType[AcceptChannel]
assert(accept.channelType_opt == Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)))
assert(accept.channelType_opt.contains(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)))
bob2alice.forward(alice)
awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL)
assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].channelFeatures.channelType == ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false))
@ -107,7 +107,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS
test("recv AcceptChannel (anchor outputs zero fee htlc txs and scid alias)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ScidAlias)) { f =>
import f._
val accept = bob2alice.expectMsgType[AcceptChannel]
assert(accept.channelType_opt == Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = false)))
assert(accept.channelType_opt.contains(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = false)))
bob2alice.forward(alice)
awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL)
assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].channelFeatures.channelType == ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = false))
@ -117,7 +117,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS
test("recv AcceptChannel (channel type not set)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f =>
import f._
val accept = bob2alice.expectMsgType[AcceptChannel]
assert(accept.channelType_opt == Some(ChannelTypes.AnchorOutputs))
assert(accept.channelType_opt.contains(ChannelTypes.AnchorOutputs))
// Alice explicitly asked for an anchor output channel. Bob doesn't support explicit channel type negotiation but
// they both activated anchor outputs so it is the default choice anyway.
bob2alice.forward(alice, accept.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty))))
@ -129,7 +129,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS
test("recv AcceptChannel (channel type not set but feature bit set)", Tag(ChannelStateTestsTags.ChannelType), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
val accept = bob2alice.expectMsgType[AcceptChannel]
assert(accept.channelType_opt == Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)))
assert(accept.channelType_opt.contains(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)))
bob2alice.forward(alice, accept.copy(tlvStream = TlvStream.empty))
alice2bob.expectMsg(Error(accept.temporaryChannelId, "option_channel_type was negotiated but channel_type is missing"))
awaitCond(alice.stateName == CLOSED)
@ -140,7 +140,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS
import f._
val accept = bob2alice.expectMsgType[AcceptChannel]
// Alice asked for a standard channel whereas they both support anchor outputs.
assert(accept.channelType_opt == Some(ChannelTypes.Standard))
assert(accept.channelType_opt.contains(ChannelTypes.Standard))
bob2alice.forward(alice, accept)
awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL)
assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].channelFeatures.channelType == ChannelTypes.Standard)
@ -150,7 +150,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS
test("recv AcceptChannel (non-default channel type not set)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag("standard-channel-type")) { f =>
import f._
val accept = bob2alice.expectMsgType[AcceptChannel]
assert(accept.channelType_opt == Some(ChannelTypes.Standard))
assert(accept.channelType_opt.contains(ChannelTypes.Standard))
// Alice asked for a standard channel whereas they both support anchor outputs. Bob doesn't support explicit channel
// type negotiation so Alice needs to abort because the channel types won't match.
bob2alice.forward(alice, accept.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty))))
@ -170,10 +170,10 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS
alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.anchorOutputsFeeratePerKw, TestConstants.feeratePerKw, aliceParams, alice2bob.ref, Init(bobParams.initFeatures), ChannelFlags.Private, channelConfig, ChannelTypes.AnchorOutputs)
bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, Init(bobParams.initFeatures), channelConfig, ChannelTypes.AnchorOutputs)
val open = alice2bob.expectMsgType[OpenChannel]
assert(open.channelType_opt == Some(ChannelTypes.AnchorOutputs))
assert(open.channelType_opt.contains(ChannelTypes.AnchorOutputs))
alice2bob.forward(bob, open)
val accept = bob2alice.expectMsgType[AcceptChannel]
assert(accept.channelType_opt == Some(ChannelTypes.AnchorOutputs))
assert(accept.channelType_opt.contains(ChannelTypes.AnchorOutputs))
bob2alice.forward(alice, accept)
awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL)
assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].channelFeatures.channelType == ChannelTypes.AnchorOutputs)
@ -183,7 +183,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS
test("recv AcceptChannel (invalid channel type)") { f =>
import f._
val accept = bob2alice.expectMsgType[AcceptChannel]
assert(accept.channelType_opt == Some(ChannelTypes.Standard))
assert(accept.channelType_opt.contains(ChannelTypes.Standard))
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"))

View file

@ -24,7 +24,7 @@ import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags}
import fr.acinq.eclair.wire.protocol.{AcceptChannel, ChannelTlv, Error, Init, OpenChannel, TlvStream}
import fr.acinq.eclair.{CltvExpiryDelta, Features, MilliSatoshiLong, TestConstants, TestKitBaseClass, ToMilliSatoshiConversion}
import fr.acinq.eclair.{CltvExpiryDelta, MilliSatoshiLong, TestConstants, TestKitBaseClass, ToMilliSatoshiConversion}
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
import org.scalatest.{Outcome, Tag}
import scodec.bits.ByteVector
@ -68,9 +68,9 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui
import f._
val open = alice2bob.expectMsgType[OpenChannel]
// Since https://github.com/lightningnetwork/lightning-rfc/pull/714 we must include an empty upfront_shutdown_script.
assert(open.upfrontShutdownScript_opt == Some(ByteVector.empty))
assert(open.upfrontShutdownScript_opt.contains(ByteVector.empty))
// We always send a channel type, even for standard channels.
assert(open.channelType_opt == Some(ChannelTypes.Standard))
assert(open.channelType_opt.contains(ChannelTypes.Standard))
alice2bob.forward(bob)
awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED)
assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].channelFeatures.channelType == ChannelTypes.Standard)
@ -79,7 +79,7 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui
test("recv OpenChannel (anchor outputs)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f =>
import f._
val open = alice2bob.expectMsgType[OpenChannel]
assert(open.channelType_opt == Some(ChannelTypes.AnchorOutputs))
assert(open.channelType_opt.contains(ChannelTypes.AnchorOutputs))
alice2bob.forward(bob)
awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED)
assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].channelFeatures.channelType == ChannelTypes.AnchorOutputs)
@ -88,7 +88,7 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui
test("recv OpenChannel (anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
val open = alice2bob.expectMsgType[OpenChannel]
assert(open.channelType_opt == Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)))
assert(open.channelType_opt.contains(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)))
alice2bob.forward(bob)
awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED)
assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].channelFeatures.channelType == ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false))
@ -97,7 +97,7 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui
test("recv OpenChannel (anchor outputs zero fee htlc txs and scid alias)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ScidAlias)) { f =>
import f._
val open = alice2bob.expectMsgType[OpenChannel]
assert(open.channelType_opt == Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = false)))
assert(open.channelType_opt.contains(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = false)))
alice2bob.forward(bob)
awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED)
assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].channelFeatures.channelType == ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = false))
@ -106,7 +106,7 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui
test("recv OpenChannel (non-default channel type)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag("standard-channel-type")) { f =>
import f._
val open = alice2bob.expectMsgType[OpenChannel]
assert(open.channelType_opt == Some(ChannelTypes.Standard))
assert(open.channelType_opt.contains(ChannelTypes.Standard))
alice2bob.forward(bob)
awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED)
assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].channelFeatures.channelType == ChannelTypes.Standard)

View file

@ -128,15 +128,25 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec {
val sender = TestProbe()
sender.send(bitcoincli, BitcoinReq("getnewaddress"))
val JString(minerAddress) = sender.expectMsgType[JValue]
// we create and announce a channel between C and F; we use push_msat to ensure both nodes have an output in the commit tx
connect(nodes("C"), nodes("F"), 5000000 sat, 500000000 msat)
generateBlocks(6, Some(minerAddress))
awaitAnnouncements(2)
// we subscribe to channel state transitions
val stateListenerC = TestProbe()
val stateListenerF = TestProbe()
nodes("C").system.eventStream.subscribe(stateListenerC.ref, classOf[ChannelStateChanged])
nodes("F").system.eventStream.subscribe(stateListenerF.ref, classOf[ChannelStateChanged])
// we create and announce a channel between C and F; we use push_msat to ensure both nodes have an output in the commit tx
connect(nodes("C"), nodes("F"), 5000000 sat, 500000000 msat)
awaitCond(stateListenerC.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == WAIT_FOR_FUNDING_CONFIRMED, max = 30 seconds)
awaitCond(stateListenerF.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == WAIT_FOR_FUNDING_CONFIRMED, max = 30 seconds)
generateBlocks(1, Some(minerAddress))
// the funder sends its channel_ready after only one block
awaitCond(stateListenerC.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == WAIT_FOR_CHANNEL_READY, max = 30 seconds)
generateBlocks(2, Some(minerAddress))
// the fundee sends its channel_ready after 3 blocks
awaitCond(stateListenerF.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == NORMAL, max = 30 seconds)
awaitCond(stateListenerC.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == NORMAL, max = 30 seconds)
// we generate more blocks for the funding tx to be deeply buried and the channel to be announced
generateBlocks(3, Some(minerAddress))
awaitAnnouncements(2)
// first we make sure we are in sync with current blockchain height
val currentBlockHeight = getBlockHeight()
awaitCond(getBlockHeight() == currentBlockHeight, max = 20 seconds, interval = 1 second)

View file

@ -19,6 +19,7 @@ class ZeroConfActivationSpec extends FixtureSpec with IntegrationPatience {
type FixtureParam = TwoNodesFixture
val ZeroConfAlice = "zero_conf_alice"
val ZeroConfBob = "zero_conf_bob"
import fr.acinq.eclair.integration.basic.fixtures.MinimalNodeFixture._
@ -26,6 +27,8 @@ class ZeroConfActivationSpec extends FixtureSpec with IntegrationPatience {
override def createFixture(testData: TestData): FixtureParam = {
// seeds have been chosen so that node ids start with 02aaaa for alice, 02bbbb for bob, etc.
val aliceParams = nodeParamsFor("alice", ByteVector32(hex"b4acd47335b25ab7b84b8c020997b12018592bb4631b868762154d77fa8b93a3"))
.modify(_.features.activated).using(_ - ZeroConf) // we will enable those features on demand
.modify(_.features.activated).usingIf(testData.tags.contains(ZeroConfAlice))(_ + (ZeroConf -> Optional))
val bobParams = nodeParamsFor("bob", ByteVector32(hex"7620226fec887b0b2ebe76492e5a3fd3eb0e47cd3773263f6a81b59a704dc492"))
.modify(_.features.activated).using(_ - ZeroConf) // we will enable those features on demand
.modify(_.features.activated).usingIf(testData.tags.contains(ZeroConfBob))(_ + (ZeroConf -> Optional))
@ -76,4 +79,19 @@ class ZeroConfActivationSpec extends FixtureSpec with IntegrationPatience {
assert(getChannelData(alice, channelId).asInstanceOf[PersistentChannelData].commitments.channelFeatures.hasFeature(ZeroConf))
assert(getChannelData(bob, channelId).asInstanceOf[PersistentChannelData].commitments.channelFeatures.hasFeature(ZeroConf))
}
test("open a channel alice-bob (zero-conf enabled on alice and bob, but not requested via channel type by alice)", Tag(ZeroConfAlice), Tag(ZeroConfBob)) { f =>
import f._
assert(alice.nodeParams.features.activated.contains(ZeroConf))
assert(bob.nodeParams.features.activated.contains(ZeroConf))
connect(alice, bob)
val channelType = AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)
val channelId = openChannel(alice, bob, 100_000 sat, channelType_opt = Some(channelType)).channelId
assert(getChannelData(alice, channelId).asInstanceOf[PersistentChannelData].commitments.channelFeatures.hasFeature(ZeroConf))
assert(getChannelData(bob, channelId).asInstanceOf[PersistentChannelData].commitments.channelFeatures.hasFeature(ZeroConf))
}
}