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

Factor funding spent handlers (#2556)

Factor and group all `WatchFundingSpentTriggered` handlers in `whenUnhandled`

Special cases (`NEGOTIATING`, `WAIT_FOR_FUTURE_`) are kept outside for readability.
This commit is contained in:
Pierre-Marie Padiou 2023-01-09 17:41:21 +01:00 committed by GitHub
parent c9c563892f
commit 93ed6e8fc6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 138 additions and 158 deletions

View file

@ -1040,88 +1040,94 @@ object Helpers {
*/
def claimCommitTxOutputs(keyManager: ChannelKeyManager, commitments: Commitments, commitTx: Transaction, db: ChannelsDb, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): Option[RevokedCommitPublished] = {
import commitments._
require(commitTx.txIn.size == 1, "commitment tx should have 1 input")
// a valid tx will always have at least one input, but this ensures we don't throw in tests
val sequence = commitTx.txIn.headOption.map(_.sequence).getOrElse(0L)
val obscuredTxNumber = Transactions.decodeTxNumber(sequence, commitTx.lockTime)
val channelKeyPath = keyManager.keyPath(localParams, channelConfig)
val obscuredTxNumber = Transactions.decodeTxNumber(commitTx.txIn.head.sequence, commitTx.lockTime)
val localPaymentPoint = localParams.walletStaticPaymentBasepoint.getOrElse(keyManager.paymentPoint(channelKeyPath).publicKey)
// this tx has been published by remote, so we need to invert local/remote params
val txNumber = Transactions.obscuredCommitTxNumber(obscuredTxNumber, !localParams.isInitiator, remoteParams.paymentBasepoint, localPaymentPoint)
require(txNumber <= 0xffffffffffffL, "txNumber must be lesser than 48 bits long")
log.warning(s"a revoked commit has been published with txnumber=$txNumber")
// now we know what commit number this tx is referring to, we can derive the commitment point from the shachain
remotePerCommitmentSecrets.getHash(0xFFFFFFFFFFFFL - txNumber)
.map(d => PrivateKey(d))
.map(remotePerCommitmentSecret => {
val remotePerCommitmentPoint = remotePerCommitmentSecret.publicKey
val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint)
val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint)
val localPaymentPubkey = Generators.derivePubKey(keyManager.paymentPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
if (txNumber > 0xffffffffffffL) {
// txNumber must be lesser than 48 bits long
None
} else {
// now we know what commit number this tx is referring to, we can derive the commitment point from the shachain
remotePerCommitmentSecrets.getHash(0xFFFFFFFFFFFFL - txNumber)
.map(d => PrivateKey(d))
.map(remotePerCommitmentSecret => {
log.warning(s"a revoked commit has been published with txnumber=$txNumber")
val feeratePerKwMain = feeEstimator.getFeeratePerKw(feeTargets.claimMainBlockTarget)
// we need to use a high fee here for punishment txs because after a delay they can be spent by the counterparty
val feeratePerKwPenalty = feeEstimator.getFeeratePerKw(target = 2)
val remotePerCommitmentPoint = remotePerCommitmentSecret.publicKey
val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint)
val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint)
val localPaymentPubkey = Generators.derivePubKey(keyManager.paymentPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
// first we will claim our main output right away
val mainTx = channelFeatures match {
case ct if ct.paysDirectlyToWallet =>
log.info(s"channel uses option_static_remotekey to pay directly to our wallet, there is nothing to do")
None
case ct => ct.commitmentFormat match {
case DefaultCommitmentFormat => withTxGenerationLog("claim-p2wpkh-output") {
Transactions.makeClaimP2WPKHOutputTx(commitTx, localParams.dustLimit, localPaymentPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwMain).map(claimMain => {
val sig = keyManager.sign(claimMain, keyManager.paymentPoint(channelKeyPath), remotePerCommitmentPoint, TxOwner.Local, commitmentFormat)
Transactions.addSigs(claimMain, localPaymentPubkey, sig)
})
}
case _: AnchorOutputsCommitmentFormat => withTxGenerationLog("remote-main-delayed") {
Transactions.makeClaimRemoteDelayedOutputTx(commitTx, localParams.dustLimit, localPaymentPoint, localParams.defaultFinalScriptPubKey, feeratePerKwMain).map(claimMain => {
val sig = keyManager.sign(claimMain, keyManager.paymentPoint(channelKeyPath), TxOwner.Local, commitmentFormat)
Transactions.addSigs(claimMain, sig)
})
val feeratePerKwMain = feeEstimator.getFeeratePerKw(feeTargets.claimMainBlockTarget)
// we need to use a high fee here for punishment txs because after a delay they can be spent by the counterparty
val feeratePerKwPenalty = feeEstimator.getFeeratePerKw(target = 2)
// first we will claim our main output right away
val mainTx = channelFeatures match {
case ct if ct.paysDirectlyToWallet =>
log.info(s"channel uses option_static_remotekey to pay directly to our wallet, there is nothing to do")
None
case ct => ct.commitmentFormat match {
case DefaultCommitmentFormat => withTxGenerationLog("claim-p2wpkh-output") {
Transactions.makeClaimP2WPKHOutputTx(commitTx, localParams.dustLimit, localPaymentPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwMain).map(claimMain => {
val sig = keyManager.sign(claimMain, keyManager.paymentPoint(channelKeyPath), remotePerCommitmentPoint, TxOwner.Local, commitmentFormat)
Transactions.addSigs(claimMain, localPaymentPubkey, sig)
})
}
case _: AnchorOutputsCommitmentFormat => withTxGenerationLog("remote-main-delayed") {
Transactions.makeClaimRemoteDelayedOutputTx(commitTx, localParams.dustLimit, localPaymentPoint, localParams.defaultFinalScriptPubKey, feeratePerKwMain).map(claimMain => {
val sig = keyManager.sign(claimMain, keyManager.paymentPoint(channelKeyPath), TxOwner.Local, commitmentFormat)
Transactions.addSigs(claimMain, sig)
})
}
}
}
}
// then we punish them by stealing their main output
val mainPenaltyTx = withTxGenerationLog("main-penalty") {
Transactions.makeMainPenaltyTx(commitTx, localParams.dustLimit, remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, feeratePerKwPenalty).map(txinfo => {
val sig = keyManager.sign(txinfo, keyManager.revocationPoint(channelKeyPath), remotePerCommitmentSecret, TxOwner.Local, commitmentFormat)
Transactions.addSigs(txinfo, sig)
})
}
// we retrieve the information needed to rebuild htlc scripts
val htlcInfos = db.listHtlcInfos(commitments.channelId, txNumber)
log.info(s"got htlcs=${htlcInfos.size} for txnumber=$txNumber")
val htlcsRedeemScripts = (
htlcInfos.map { case (paymentHash, cltvExpiry) => Scripts.htlcReceived(remoteHtlcPubkey, localHtlcPubkey, remoteRevocationPubkey, Crypto.ripemd160(paymentHash), cltvExpiry, commitmentFormat) } ++
htlcInfos.map { case (paymentHash, _) => Scripts.htlcOffered(remoteHtlcPubkey, localHtlcPubkey, remoteRevocationPubkey, Crypto.ripemd160(paymentHash), commitmentFormat) }
)
.map(redeemScript => Script.write(pay2wsh(redeemScript)) -> Script.write(redeemScript))
.toMap
// and finally we steal the htlc outputs
val htlcPenaltyTxs = commitTx.txOut.zipWithIndex.collect { case (txOut, outputIndex) if htlcsRedeemScripts.contains(txOut.publicKeyScript) =>
val htlcRedeemScript = htlcsRedeemScripts(txOut.publicKeyScript)
withTxGenerationLog("htlc-penalty") {
Transactions.makeHtlcPenaltyTx(commitTx, outputIndex, htlcRedeemScript, localParams.dustLimit, localParams.defaultFinalScriptPubKey, feeratePerKwPenalty).map(htlcPenalty => {
val sig = keyManager.sign(htlcPenalty, keyManager.revocationPoint(channelKeyPath), remotePerCommitmentSecret, TxOwner.Local, commitmentFormat)
Transactions.addSigs(htlcPenalty, sig, remoteRevocationPubkey)
// then we punish them by stealing their main output
val mainPenaltyTx = withTxGenerationLog("main-penalty") {
Transactions.makeMainPenaltyTx(commitTx, localParams.dustLimit, remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, feeratePerKwPenalty).map(txinfo => {
val sig = keyManager.sign(txinfo, keyManager.revocationPoint(channelKeyPath), remotePerCommitmentSecret, TxOwner.Local, commitmentFormat)
Transactions.addSigs(txinfo, sig)
})
}
}.toList.flatten
RevokedCommitPublished(
commitTx = commitTx,
claimMainOutputTx = mainTx,
mainPenaltyTx = mainPenaltyTx,
htlcPenaltyTxs = htlcPenaltyTxs,
claimHtlcDelayedPenaltyTxs = Nil, // we will generate and spend those if they publish their HtlcSuccessTx or HtlcTimeoutTx
irrevocablySpent = Map.empty
)
})
// we retrieve the information needed to rebuild htlc scripts
val htlcInfos = db.listHtlcInfos(commitments.channelId, txNumber)
log.info(s"got htlcs=${htlcInfos.size} for txnumber=$txNumber")
val htlcsRedeemScripts = (
htlcInfos.map { case (paymentHash, cltvExpiry) => Scripts.htlcReceived(remoteHtlcPubkey, localHtlcPubkey, remoteRevocationPubkey, Crypto.ripemd160(paymentHash), cltvExpiry, commitmentFormat) } ++
htlcInfos.map { case (paymentHash, _) => Scripts.htlcOffered(remoteHtlcPubkey, localHtlcPubkey, remoteRevocationPubkey, Crypto.ripemd160(paymentHash), commitmentFormat) }
)
.map(redeemScript => Script.write(pay2wsh(redeemScript)) -> Script.write(redeemScript))
.toMap
// and finally we steal the htlc outputs
val htlcPenaltyTxs = commitTx.txOut.zipWithIndex.collect { case (txOut, outputIndex) if htlcsRedeemScripts.contains(txOut.publicKeyScript) =>
val htlcRedeemScript = htlcsRedeemScripts(txOut.publicKeyScript)
withTxGenerationLog("htlc-penalty") {
Transactions.makeHtlcPenaltyTx(commitTx, outputIndex, htlcRedeemScript, localParams.dustLimit, localParams.defaultFinalScriptPubKey, feeratePerKwPenalty).map(htlcPenalty => {
val sig = keyManager.sign(htlcPenalty, keyManager.revocationPoint(channelKeyPath), remotePerCommitmentSecret, TxOwner.Local, commitmentFormat)
Transactions.addSigs(htlcPenalty, sig, remoteRevocationPubkey)
})
}
}.toList.flatten
RevokedCommitPublished(
commitTx = commitTx,
claimMainOutputTx = mainTx,
mainPenaltyTx = mainPenaltyTx,
htlcPenaltyTxs = htlcPenaltyTxs,
claimHtlcDelayedPenaltyTxs = Nil, // we will generate and spend those if they publish their HtlcSuccessTx or HtlcTimeoutTx
irrevocablySpent = Map.empty
)
})
}
}
/**

View file

@ -701,12 +701,6 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val
goto(NORMAL) using d.copy(channelUpdate = channelUpdate1) storing()
}
case Event(WatchFundingSpentTriggered(tx), d: DATA_NORMAL) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
case Event(WatchFundingSpentTriggered(tx), d: DATA_NORMAL) if d.commitments.remoteNextCommitInfo.left.toOption.exists(_.nextRemoteCommit.txid == tx.txid) => handleRemoteSpentNext(tx, d)
case Event(WatchFundingSpentTriggered(tx), d: DATA_NORMAL) => handleRemoteSpentOther(tx, d)
case Event(INPUT_DISCONNECTED, d: DATA_NORMAL) =>
// we cancel the timer that would have made us send the enabled update after reconnection (flappy channel protection)
cancelTimer(Reconnected.toString)
@ -908,12 +902,6 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val
case Event(c: CurrentFeerates, d: DATA_SHUTDOWN) => handleCurrentFeerate(c, d)
case Event(WatchFundingSpentTriggered(tx), d: DATA_SHUTDOWN) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
case Event(WatchFundingSpentTriggered(tx), d: DATA_SHUTDOWN) if d.commitments.remoteNextCommitInfo.left.toOption.exists(_.nextRemoteCommit.txid == tx.txid) => handleRemoteSpentNext(tx, d)
case Event(WatchFundingSpentTriggered(tx), d: DATA_SHUTDOWN) => handleRemoteSpentOther(tx, d)
case Event(c: CMD_CLOSE, d: DATA_SHUTDOWN) =>
c.feerates match {
case Some(feerates) if c.feerates != d.closingFeerates =>
@ -1016,21 +1004,6 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val
case Left(cause) => handleLocalError(cause, d, Some(c))
}
case Event(WatchFundingSpentTriggered(tx), d: DATA_NEGOTIATING) if d.closingTxProposed.flatten.exists(_.unsignedTx.tx.txid == tx.txid) =>
// they can publish a closing tx with any sig we sent them, even if we are not done negotiating
handleMutualClose(getMutualClosePublished(tx, d.closingTxProposed), Left(d))
case Event(WatchFundingSpentTriggered(tx), d: DATA_NEGOTIATING) if d.bestUnpublishedClosingTx_opt.exists(_.tx.txid == tx.txid) =>
log.warning(s"looks like a mutual close tx has been published from the outside of the channel: closingTxId=${tx.txid}")
// if we were in the process of closing and already received a closing sig from the counterparty, it's always better to use that
handleMutualClose(d.bestUnpublishedClosingTx_opt.get, Left(d))
case Event(WatchFundingSpentTriggered(tx), d: DATA_NEGOTIATING) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
case Event(WatchFundingSpentTriggered(tx), d: DATA_NEGOTIATING) if d.commitments.remoteNextCommitInfo.left.toOption.exists(_.nextRemoteCommit.txid == tx.txid) => handleRemoteSpentNext(tx, d)
case Event(WatchFundingSpentTriggered(tx), d: DATA_NEGOTIATING) => handleRemoteSpentOther(tx, d)
case Event(c: CMD_CLOSE, d: DATA_NEGOTIATING) =>
c.feerates match {
case Some(feerates) =>
@ -1337,18 +1310,6 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val
case Event(_: WatchFundingConfirmedTriggered, _) => stay()
case Event(_: WatchFundingDeeplyBuriedTriggered, _) => stay()
case Event(WatchFundingSpentTriggered(tx), d: DATA_NEGOTIATING) if d.closingTxProposed.flatten.exists(_.unsignedTx.tx.txid == tx.txid) =>
handleMutualClose(getMutualClosePublished(tx, d.closingTxProposed), Left(d))
case Event(WatchFundingSpentTriggered(tx), d: PersistentChannelData) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
case Event(WatchFundingSpentTriggered(tx), d: PersistentChannelData) if d.commitments.remoteNextCommitInfo.left.toOption.exists(_.nextRemoteCommit.txid == tx.txid) => handleRemoteSpentNext(tx, d)
case Event(WatchFundingSpentTriggered(tx), d: DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT) => handleRemoteSpentFuture(tx, d)
case Event(WatchFundingSpentTriggered(tx), d: PersistentChannelData) => handleRemoteSpentOther(tx, d)
})
when(SYNCING)(handleExceptions {
@ -1540,14 +1501,6 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val
case Event(_: WatchFundingDeeplyBuriedTriggered, _) => stay()
case Event(WatchFundingSpentTriggered(tx), d: DATA_NEGOTIATING) if d.closingTxProposed.flatten.exists(_.unsignedTx.tx.txid == tx.txid) => handleMutualClose(getMutualClosePublished(tx, d.closingTxProposed), Left(d))
case Event(WatchFundingSpentTriggered(tx), d: PersistentChannelData) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
case Event(WatchFundingSpentTriggered(tx), d: PersistentChannelData) if d.commitments.remoteNextCommitInfo.left.toOption.exists(_.nextRemoteCommit.txid == tx.txid) => handleRemoteSpentNext(tx, d)
case Event(WatchFundingSpentTriggered(tx), d: PersistentChannelData) => handleRemoteSpentOther(tx, d)
case Event(e: Error, d: PersistentChannelData) => handleRemoteError(e, d)
})
@ -1634,9 +1587,30 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val
// peer doesn't cancel the timer
case Event(TickChannelOpenTimeout, _) => stay()
case Event(WatchFundingSpentTriggered(tx), d: PersistentChannelData) if tx.txid == d.commitments.localCommit.commitTxAndRemoteSig.commitTx.tx.txid =>
log.warning(s"processing local commit spent in catch-all handler")
spendLocalCurrent(d)
// we declare WatchFundingSpentTriggered handlers here because they apply to variants of each state in OFFLINE/SYNCING
case Event(WatchFundingSpentTriggered(tx), d: DATA_NEGOTIATING) if d.closingTxProposed.flatten.exists(_.unsignedTx.tx.txid == tx.txid) =>
// they can publish a closing tx with any sig we sent them, even if we are not done negotiating
handleMutualClose(getMutualClosePublished(tx, d.closingTxProposed), Left(d))
case Event(WatchFundingSpentTriggered(tx), d: DATA_NEGOTIATING) if d.bestUnpublishedClosingTx_opt.exists(_.tx.txid == tx.txid) =>
log.warning(s"looks like a mutual close tx has been published from the outside of the channel: closingTxId=${tx.txid}")
// if we were in the process of closing and already received a closing sig from the counterparty, it's always better to use that
handleMutualClose(d.bestUnpublishedClosingTx_opt.get, Left(d))
case Event(WatchFundingSpentTriggered(tx), d: DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT) => handleRemoteSpentFuture(tx, d)
case Event(WatchFundingSpentTriggered(tx), d: PersistentChannelData) =>
if (tx.txid == d.commitments.remoteCommit.txid) {
handleRemoteSpentCurrent(tx, d)
} else if (d.commitments.remoteNextCommitInfo.left.toOption.exists(_.nextRemoteCommit.txid == tx.txid)) {
handleRemoteSpentNext(tx, d)
} else if (tx.txid == d.commitments.localCommit.commitTxAndRemoteSig.commitTx.tx.txid) {
log.warning(s"processing local commit spent from the outside")
spendLocalCurrent(d)
} else {
handleRemoteSpentOther(tx, d)
}
}
onTransition {

View file

@ -641,10 +641,6 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers {
delayEarlyAnnouncementSigs(remoteAnnSigs)
stay()
case Event(WatchFundingSpentTriggered(tx), d: DATA_WAIT_FOR_DUAL_FUNDING_READY) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
case Event(WatchFundingSpentTriggered(tx), d: DATA_WAIT_FOR_DUAL_FUNDING_READY) => handleInformationLeak(tx, d)
case Event(e: Error, d: DATA_WAIT_FOR_DUAL_FUNDING_READY) => handleRemoteError(e, d)
})

View file

@ -421,10 +421,6 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers {
case Event(BITCOIN_FUNDING_TIMEOUT, d: DATA_WAIT_FOR_FUNDING_CONFIRMED) => handleFundingTimeout(d)
case Event(WatchFundingSpentTriggered(tx), d: DATA_WAIT_FOR_FUNDING_CONFIRMED) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
case Event(WatchFundingSpentTriggered(tx), d: DATA_WAIT_FOR_FUNDING_CONFIRMED) => handleInformationLeak(tx, d)
case Event(e: Error, d: DATA_WAIT_FOR_FUNDING_CONFIRMED) => handleRemoteError(e, d)
})
@ -437,10 +433,6 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers {
delayEarlyAnnouncementSigs(remoteAnnSigs)
stay()
case Event(WatchFundingSpentTriggered(tx), d: DATA_WAIT_FOR_CHANNEL_READY) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
case Event(WatchFundingSpentTriggered(tx), d: DATA_WAIT_FOR_CHANNEL_READY) => handleInformationLeak(tx, d)
case Event(e: Error, d: DATA_WAIT_FOR_CHANNEL_READY) => handleRemoteError(e, d)
})

View file

@ -326,20 +326,6 @@ trait ErrorHandlers extends CommonHandlers {
watchSpentIfNeeded(commitTx, watchSpentQueue, irrevocablySpent)
}
def handleInformationLeak(tx: Transaction, d: PersistentChannelData) = {
// this is never supposed to happen !!
log.error(s"our funding tx ${d.commitments.fundingTxId} was spent by txid=${tx.txid}!!")
context.system.eventStream.publish(NotifyNodeOperator(NotificationsLogger.Error, s"funding tx ${d.commitments.fundingTxId} of channel ${d.channelId} was spent by an unknown transaction, indicating that your DB has lost data or your node has been breached: please contact the dev team."))
val exc = FundingTxSpent(d.channelId, tx.txid)
val error = Error(d.channelId, exc.getMessage)
// let's try to spend our current local tx
val commitTx = d.commitments.fullySignedLocalCommitTx(keyManager).tx
val localCommitPublished = Closing.LocalClose.claimCommitTxOutputs(keyManager, d.commitments, commitTx, nodeParams.currentBlockHeight, nodeParams.onChainFeeConf)
goto(ERR_INFORMATION_LEAK) calling doPublish(localCommitPublished, d.commitments) sending error
}
def handleOutdatedCommitment(channelReestablish: ChannelReestablish, d: PersistentChannelData) = {
val exc = PleasePublishYourCommitment(d.channelId)
val error = Error(d.channelId, exc.getMessage)

View file

@ -245,11 +245,7 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu
test("recv WatchFundingSpentTriggered (other commit)") { f =>
import f._
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx
alice ! WatchFundingSpentTriggered(Transaction(0, Nil, Nil, 0))
alice2bob.expectMsgType[Error]
assert(alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx].tx.txid == tx.txid)
alice2blockchain.expectMsgType[TxPublisher.PublishTx]
awaitCond(alice.stateName == ERR_INFORMATION_LEAK)
}

View file

@ -19,7 +19,7 @@ package fr.acinq.eclair.channel.states.c
import akka.actor.Status
import akka.actor.typed.scaladsl.adapter.actorRefAdapter
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.bitcoin.scalacompat.{ByteVector32, SatoshiLong}
import fr.acinq.bitcoin.scalacompat.{ByteVector32, SatoshiLong, Transaction}
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._
import fr.acinq.eclair.blockchain.{CurrentBlockHeight, SingleKeyOnChainWallet}
import fr.acinq.eclair.channel.InteractiveTxBuilder.FullySignedSharedTransaction
@ -574,6 +574,12 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
awaitCond(bob2.stateName == CLOSING)
}
test("recv WatchFundingSpentTriggered (other commit)", Tag(ChannelStateTestsTags.DualFunding)) { f =>
import f._
alice ! WatchFundingSpentTriggered(Transaction(0, Nil, Nil, 0))
awaitCond(alice.stateName == ERR_INFORMATION_LEAK)
}
test("recv Error", Tag(ChannelStateTestsTags.DualFunding)) { f =>
import f._
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx

View file

@ -184,10 +184,7 @@ class WaitForDualFundingReadyStateSpec extends TestKitBaseClass with FixtureAnyF
test("recv WatchFundingSpentTriggered (other commit)", Tag(ChannelStateTestsTags.DualFunding), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._
alice2bob.expectMsgType[ChannelReady]
val commitTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_READY].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx
alice ! WatchFundingSpentTriggered(Transaction(0, Nil, Nil, 0))
alice2bob.expectMsgType[Error]
assert(alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx].tx.txid == commitTx.txid)
awaitCond(alice.stateName == ERR_INFORMATION_LEAK)
}

View file

@ -236,10 +236,7 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF
test("recv WatchFundingSpentTriggered (other commit)") { f =>
import f._
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx
alice ! WatchFundingSpentTriggered(Transaction(0, Nil, Nil, 0))
alice2bob.expectMsgType[Error]
assert(alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx].tx.txid == tx.txid)
awaitCond(alice.stateName == ERR_INFORMATION_LEAK)
}

View file

@ -3256,6 +3256,12 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(addSettled.htlc == htlc3)
}
test("recv WatchFundingSpentTriggered (other commit)") { f =>
import f._
alice ! WatchFundingSpentTriggered(Transaction(0, Nil, Nil, 0))
awaitCond(alice.stateName == ERR_INFORMATION_LEAK)
}
test("recv Error") { f =>
import f._
val (ra1, htlca1) = addHtlc(250000000 msat, alice, bob, alice2bob, bob2alice)

View file

@ -799,6 +799,12 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
channelUpdateListener.expectMsgType[LocalChannelUpdate]
}
test("recv WatchFundingSpentTriggered (other commit)") { f =>
import f._
alice ! WatchFundingSpentTriggered(Transaction(0, Nil, Nil, 0))
awaitCond(alice.stateName == ERR_INFORMATION_LEAK)
}
def disconnect(alice: TestFSMRef[ChannelState, ChannelData, Channel], bob: TestFSMRef[ChannelState, ChannelData, Channel]): Unit = {
alice ! INPUT_DISCONNECTED
bob ! INPUT_DISCONNECTED

View file

@ -900,6 +900,12 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
alice2blockchain.expectNoMessage(1 second)
}
test("recv WatchFundingSpentTriggered (other commit)") { f =>
import f._
alice ! WatchFundingSpentTriggered(Transaction(0, Nil, Nil, 0))
awaitCond(alice.stateName == ERR_INFORMATION_LEAK)
}
test("recv Error") { f =>
import f._
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx

View file

@ -17,7 +17,7 @@
package fr.acinq.eclair.channel.states.g
import akka.testkit.TestProbe
import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Satoshi, SatoshiLong}
import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Satoshi, SatoshiLong, Transaction}
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._
import fr.acinq.eclair.blockchain.fee.{FeeratePerKw, FeeratesPerKw}
import fr.acinq.eclair.channel.Helpers.Closing
@ -547,6 +547,12 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
awaitCond(bob.stateName == CLOSING)
}
test("recv WatchFundingSpentTriggered (other commit)") { f =>
import f._
alice ! WatchFundingSpentTriggered(Transaction(0, Nil, Nil, 0))
awaitCond(alice.stateName == ERR_INFORMATION_LEAK)
}
test("recv Error") { f =>
import f._
bobClose(f)

View file

@ -1629,6 +1629,12 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(new String(error.data.toArray) == FundingTxSpent(channelId(alice), initialState.spendingTxs.head.txid).getMessage)
}
test("recv WatchFundingSpentTriggered (other commit)") { f =>
import f._
alice ! WatchFundingSpentTriggered(Transaction(0, Nil, Nil, 0))
awaitCond(alice.stateName == ERR_INFORMATION_LEAK)
}
test("recv CMD_CLOSE") { f =>
import f._
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)