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

Ignore pre-generated shutdown script when possible (#2738)

This is a follow up to #2565, more precisely to the _caveat_ here: https://github.com/ACINQ/eclair/pull/2565#issuecomment-1397052582.

We now create a fresh shutdown script even if one was already generated at channel creation, if the channel doesn't have the mandatory `option_upfront_shutdown_script` negotiated.

The (reasonable) assumption is that other implementations will ignore our pre-generated script if they didn't support the `option_upfront_shutdown_script` feature.

This "on-the-fly" approach is simpler and safer than a db migration.
This commit is contained in:
Pierre-Marie Padiou 2023-09-07 21:48:44 +02:00 committed by GitHub
parent 8d4205271d
commit 841a8d9b19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 23 additions and 5 deletions

View File

@ -125,7 +125,9 @@ case class ChannelParams(channelId: ByteVector32,
// README: if we set our bitcoin node to generate taproot addresses and our peer does not support option_shutdown_anysegwit, we will not be able to mutual-close // README: if we set our bitcoin node to generate taproot addresses and our peer does not support option_shutdown_anysegwit, we will not be able to mutual-close
// channels as the isValidFinalScriptPubkey() check would fail. // channels as the isValidFinalScriptPubkey() check would fail.
val allowAnySegwit = Features.canUseFeature(localParams.initFeatures, remoteParams.initFeatures, Features.ShutdownAnySegwit) val allowAnySegwit = Features.canUseFeature(localParams.initFeatures, remoteParams.initFeatures, Features.ShutdownAnySegwit)
if (localParams.upfrontShutdownScript_opt.exists(_ != localScriptPubKey)) Left(InvalidFinalScript(channelId)) val mustUseUpfrontShutdownScript = channelFeatures.hasFeature(Features.UpfrontShutdownScript)
// we only enforce using the pre-generated shutdown script if option_upfront_shutdown_script is set
if (mustUseUpfrontShutdownScript && localParams.upfrontShutdownScript_opt.exists(_ != localScriptPubKey)) Left(InvalidFinalScript(channelId))
else if (!Closing.MutualClose.isValidFinalScriptPubkey(localScriptPubKey, allowAnySegwit)) Left(InvalidFinalScript(channelId)) else if (!Closing.MutualClose.isValidFinalScriptPubkey(localScriptPubKey, allowAnySegwit)) Left(InvalidFinalScript(channelId))
else Right(localScriptPubKey) else Right(localScriptPubKey)
} }
@ -137,7 +139,9 @@ case class ChannelParams(channelId: ByteVector32,
def validateRemoteShutdownScript(remoteScriptPubKey: ByteVector): Either[ChannelException, ByteVector] = { def validateRemoteShutdownScript(remoteScriptPubKey: ByteVector): Either[ChannelException, ByteVector] = {
// to check whether shutdown_any_segwit is active we check features in local and remote parameters, which are negotiated each time we connect to our peer. // to check whether shutdown_any_segwit is active we check features in local and remote parameters, which are negotiated each time we connect to our peer.
val allowAnySegwit = Features.canUseFeature(localParams.initFeatures, remoteParams.initFeatures, Features.ShutdownAnySegwit) val allowAnySegwit = Features.canUseFeature(localParams.initFeatures, remoteParams.initFeatures, Features.ShutdownAnySegwit)
if (localParams.upfrontShutdownScript_opt.isDefined && remoteParams.upfrontShutdownScript_opt.exists(_ != remoteScriptPubKey)) Left(InvalidFinalScript(channelId)) val mustUseUpfrontShutdownScript = channelFeatures.hasFeature(Features.UpfrontShutdownScript)
// we only enforce using the pre-generated shutdown script if option_upfront_shutdown_script is set
if (mustUseUpfrontShutdownScript && remoteParams.upfrontShutdownScript_opt.exists(_ != remoteScriptPubKey)) Left(InvalidFinalScript(channelId))
else if (!Closing.MutualClose.isValidFinalScriptPubkey(remoteScriptPubKey, allowAnySegwit)) Left(InvalidFinalScript(channelId)) else if (!Closing.MutualClose.isValidFinalScriptPubkey(remoteScriptPubKey, allowAnySegwit)) Left(InvalidFinalScript(channelId))
else Right(remoteScriptPubKey) else Right(remoteScriptPubKey)
} }

View File

@ -634,7 +634,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
} }
if (d.remoteShutdown.isDefined && !commitments1.changes.localHasUnsignedOutgoingHtlcs) { if (d.remoteShutdown.isDefined && !commitments1.changes.localHasUnsignedOutgoingHtlcs) {
// we were waiting for our pending htlcs to be signed before replying with our local shutdown // we were waiting for our pending htlcs to be signed before replying with our local shutdown
val finalScriptPubKey = commitments1.params.localParams.upfrontShutdownScript_opt.getOrElse(getOrGenerateFinalScriptPubKey(d)) val finalScriptPubKey = getOrGenerateFinalScriptPubKey(d)
val localShutdown = Shutdown(d.channelId, finalScriptPubKey) val localShutdown = Shutdown(d.channelId, finalScriptPubKey)
// note: it means that we had pending htlcs to sign, therefore we go to SHUTDOWN, not to NEGOTIATING // note: it means that we had pending htlcs to sign, therefore we go to SHUTDOWN, not to NEGOTIATING
require(commitments1.latest.remoteCommit.spec.htlcs.nonEmpty, "we must have just signed new htlcs, otherwise we would have sent our Shutdown earlier") require(commitments1.latest.remoteCommit.spec.htlcs.nonEmpty, "we must have just signed new htlcs, otherwise we would have sent our Shutdown earlier")
@ -656,7 +656,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
} else if (d.commitments.changes.localHasUnsignedOutgoingUpdateFee) { } else if (d.commitments.changes.localHasUnsignedOutgoingUpdateFee) {
handleCommandError(CannotCloseWithUnsignedOutgoingUpdateFee(d.channelId), c) handleCommandError(CannotCloseWithUnsignedOutgoingUpdateFee(d.channelId), c)
} else { } else {
val localScriptPubKey = c.scriptPubKey.getOrElse(d.commitments.params.localParams.upfrontShutdownScript_opt.getOrElse(getOrGenerateFinalScriptPubKey(d))) val localScriptPubKey = c.scriptPubKey.getOrElse(getOrGenerateFinalScriptPubKey(d))
d.commitments.params.validateLocalShutdownScript(localScriptPubKey) match { d.commitments.params.validateLocalShutdownScript(localScriptPubKey) match {
case Left(e) => handleCommandError(e, c) case Left(e) => handleCommandError(e, c)
case Right(localShutdownScript) => case Right(localShutdownScript) =>

View File

@ -18,6 +18,7 @@ package fr.acinq.eclair.channel.fsm
import akka.actor.{ActorRef, FSM, Status} import akka.actor.{ActorRef, FSM, Status}
import fr.acinq.bitcoin.scalacompat.{ByteVector32, Script} import fr.acinq.bitcoin.scalacompat.{ByteVector32, Script}
import fr.acinq.eclair.Features
import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel._
import fr.acinq.eclair.db.PendingCommandsDb import fr.acinq.eclair.db.PendingCommandsDb
import fr.acinq.eclair.io.Peer import fr.acinq.eclair.io.Peer
@ -106,7 +107,20 @@ trait CommonHandlers {
case d: DATA_SHUTDOWN => d.localShutdown.scriptPubKey case d: DATA_SHUTDOWN => d.localShutdown.scriptPubKey
case d: DATA_NEGOTIATING => d.localShutdown.scriptPubKey case d: DATA_NEGOTIATING => d.localShutdown.scriptPubKey
case d: DATA_CLOSING => d.finalScriptPubKey case d: DATA_CLOSING => d.finalScriptPubKey
case d => d.commitments.params.localParams.upfrontShutdownScript_opt.getOrElse(generateFinalScriptPubKey()) case d =>
d.commitments.params.localParams.upfrontShutdownScript_opt match {
case Some(upfrontShutdownScript) =>
if (data.commitments.params.channelFeatures.hasFeature(Features.UpfrontShutdownScript)) {
// we have a shutdown script, and the option_upfront_shutdown_script is enabled: we have to use it
upfrontShutdownScript
} else {
log.info("ignoring pre-generated shutdown script, because option_upfront_shutdown_script is disabled")
generateFinalScriptPubKey()
}
case None =>
// normal case: we don't pre-generate shutdown scripts
generateFinalScriptPubKey()
}
} }
private def generateFinalScriptPubKey(): ByteVector = { private def generateFinalScriptPubKey(): ByteVector = {