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:
parent
a1f7c1e74f
commit
2790b2ff6c
11 changed files with 112 additions and 45 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
"""
|
||||
|
|
|
@ -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}"))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Reference in a new issue