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:
parent
c9c563892f
commit
93ed6e8fc6
14 changed files with 138 additions and 158 deletions
|
@ -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
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue