Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,8 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
// we keep track of the splice_locked we sent after channel_reestablish and it's funding tx index to avoid sending it again
// TODO: we can remove that once we stop supporting the legacy splicing protocol
private var spliceLockedSent = Map.empty[TxId, Long]
// we keep track of invalid interactive-tx signatures we receive
var invalidTxSigsReceived = Map.empty[TxId, Int]

private def trimAnnouncementSigsStashIfNeeded(): Unit = {
if (announcementSigsStash.size >= 10) {
Expand Down Expand Up @@ -871,7 +873,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall

case Event(ProcessCurrentBlockHeight(c), d: DATA_NORMAL) => handleNewBlock(c, d)

case Event(c: CurrentFeerates.BitcoinCore, d: DATA_NORMAL) => handleCurrentFeerate(d)
case Event(_: CurrentFeerates.BitcoinCore, d: DATA_NORMAL) => handleCurrentFeerate(d)

case Event(_: ChannelReady, d: DATA_NORMAL) =>
// After a reconnection, if the channel hasn't been used yet, our peer cannot be sure we received their channel_ready
Expand Down Expand Up @@ -1438,22 +1440,31 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
case Event(msg: TxSignatures, d: DATA_NORMAL) =>
d.commitments.latest.localFundingStatus match {
case dfu@LocalFundingStatus.DualFundedUnconfirmedFundingTx(fundingTx: PartiallySignedSharedTransaction, _, _, _) if fundingTx.txId == msg.txId =>
// we already sent our tx_signatures
InteractiveTxSigningSession.addRemoteSigs(channelKeys, dfu.fundingParams, fundingTx, msg) match {
case Left(cause) =>
log.warning("received invalid tx_signatures for fundingTxId={}: {}", msg.txId, cause.getMessage)
// The funding transaction may still confirm (since our peer should be able to generate valid signatures),
// so we cannot close the channel yet.
stay() sending Error(d.channelId, InvalidFundingSignature(d.channelId, Some(fundingTx.txId)).getMessage)
case Right(fundingTx) =>
val dfu1 = dfu.copy(sharedTx = fundingTx)
d.commitments.updateLocalFundingStatus(msg.txId, dfu1, d.lastAnnouncedFundingTxId_opt) match {
case Right((commitments1, _)) =>
log.info("publishing funding tx for channelId={} fundingTxId={}", d.channelId, fundingTx.signedTx.txid)
Metrics.recordSplice(dfu.fundingParams, fundingTx.tx)
stay() using d.copy(commitments = commitments1) storing() calling publishFundingTx(dfu1)
case Left(_) =>
stay()
// We already sent our tx_signatures: the transaction may be broadcast and may confirm in the next blocks.
invalidTxSigsReceived.get(msg.txId) match {
case Some(count) if count >= 3 =>
// Our peer already sent us 3 invalid tx_signatures for that splice: they're either buggy or malicious,
// so we stop processing their additional tx_signatures and simply wait for the transaction to confirm.
log.warning("received too many invalid tx_signatures for fundingTxId={}, ignoring this one", msg.txId)
stay()
case _ =>
InteractiveTxSigningSession.addRemoteSigs(channelKeys, dfu.fundingParams, fundingTx, msg) match {
case Left(cause) =>
log.warning("received invalid tx_signatures for fundingTxId={}: {}", msg.txId, cause.getMessage)
// The funding transaction may still confirm (since our peer should be able to generate valid signatures),
// so we cannot close the channel yet. We record that they sent an invalid signature though.
invalidTxSigsReceived += (msg.txId -> (invalidTxSigsReceived.getOrElse(msg.txId, 0) + 1))
stay() sending Error(d.channelId, InvalidFundingSignature(d.channelId, Some(fundingTx.txId)).getMessage)
case Right(fundingTx) =>
val dfu1 = dfu.copy(sharedTx = fundingTx)
d.commitments.updateLocalFundingStatus(msg.txId, dfu1, d.lastAnnouncedFundingTxId_opt) match {
case Right((commitments1, _)) =>
log.info("publishing funding tx for channelId={} fundingTxId={}", d.channelId, fundingTx.signedTx.txid)
Metrics.recordSplice(dfu.fundingParams, fundingTx.tx)
stay() using d.copy(commitments = commitments1) storing() calling publishFundingTx(dfu1)
case Left(_) =>
stay()
}
}
}
case _ =>
Expand Down Expand Up @@ -1489,7 +1500,10 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall

case Event(w: WatchFundingConfirmedTriggered, d: DATA_NORMAL) =>
handleFundingConfirmedWhileConnected(w, d) match {
case Some((commitments1, spliceLocked_opt, annSigs_opt)) => stay() using d.copy(commitments = commitments1) storing() sending spliceLocked_opt.toSeq ++ annSigs_opt.toSeq
case Some((commitments1, spliceLocked_opt, annSigs_opt)) =>
// We reset the counter for *all* invalid tx_signatures received for simplicity whenever a splice confirms.
invalidTxSigsReceived = Map.empty
stay() using d.copy(commitments = commitments1) storing() sending spliceLocked_opt.toSeq ++ annSigs_opt.toSeq
case None => stay()
}

Expand Down Expand Up @@ -1744,13 +1758,14 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
log.debug("our peer updated their shutdown script (previous={}, current={})", d.remoteShutdown.scriptPubKey, shutdown.scriptPubKey)
}
remoteCloseeNonce_opt = shutdown.closeeNonce_opt
stay() using d.copy(remoteShutdown = shutdown) storing()
// No need to persist the change in the DB: they will re-send shutdown on reconnection.
stay() using d.copy(remoteShutdown = shutdown)

case Event(r: RevocationTimeout, d: DATA_SHUTDOWN) => handleRevocationTimeout(r, d)

case Event(ProcessCurrentBlockHeight(c), d: DATA_SHUTDOWN) => handleNewBlock(c, d)

case Event(c: CurrentFeerates.BitcoinCore, d: DATA_SHUTDOWN) => handleCurrentFeerate(d)
case Event(_: CurrentFeerates.BitcoinCore, d: DATA_SHUTDOWN) => handleCurrentFeerate(d)

case Event(c: CMD_CLOSE, d: DATA_SHUTDOWN) =>
val useSimpleClose = Features.canUseFeature(d.commitments.localChannelParams.initFeatures, d.commitments.remoteChannelParams.initFeatures, Features.SimpleClose)
Expand Down Expand Up @@ -1781,7 +1796,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
if (remoteShutdown.scriptPubKey != d.remoteShutdown.scriptPubKey) {
// This may lead to a signature mismatch if our peer changed their script without using option_simple_close.
log.warning("received shutdown changing remote script, this may lead to a signature mismatch: previous={}, current={}", d.remoteShutdown.scriptPubKey, remoteShutdown.scriptPubKey)
stay() using d.copy(remoteShutdown = remoteShutdown) storing()
stay() using d.copy(remoteShutdown = remoteShutdown)
} else {
stay()
}
Expand Down Expand Up @@ -1890,7 +1905,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
if (shutdown.scriptPubKey != d.remoteScriptPubKey) {
// This may lead to a signature mismatch: peers must use closing_complete to update their closing script.
log.warning("received shutdown changing remote script, this may lead to a signature mismatch: previous={}, current={}", d.remoteScriptPubKey, shutdown.scriptPubKey)
stay() using d.copy(remoteScriptPubKey = shutdown.scriptPubKey) storing()
stay() using d.copy(remoteScriptPubKey = shutdown.scriptPubKey)
} else {
stay()
}
Expand Down Expand Up @@ -2867,7 +2882,10 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall

case Event(w: WatchFundingConfirmedTriggered, d: DATA_NORMAL) =>
handleFundingConfirmedWhileConnected(w, d) match {
case Some((commitments1, spliceLocked_opt, annSigs_opt)) => stay() using d.copy(commitments = commitments1) storing() sending spliceLocked_opt.toSeq ++ annSigs_opt.toSeq
case Some((commitments1, spliceLocked_opt, annSigs_opt)) =>
// We reset the counter for *all* invalid tx_signatures received for simplicity whenever a splice confirms.
invalidTxSigsReceived = Map.empty
stay() using d.copy(commitments = commitments1) storing() sending spliceLocked_opt.toSeq ++ annSigs_opt.toSeq
case None => stay()
}

Expand Down Expand Up @@ -3052,6 +3070,8 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
case d: DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT => d.copy(commitments = commitments1)
case d: DATA_CLOSING => d // there is a dedicated handler in CLOSING state
}
// We reset the counter for *all* invalid tx_signatures received for simplicity whenever a splice confirms.
invalidTxSigsReceived = Map.empty
stay() using d1 storing()
case Left(_) => stay()
}
Expand Down Expand Up @@ -3248,7 +3268,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
case _ -> OFFLINE =>
announcementSigsStash = Map.empty
announcementSigsSent = Set.empty
spliceLockedSent = Map.empty[TxId, Long]
spliceLockedSent = Map.empty
remoteNextCommitNonces = Map.empty
localCloseeNonce_opt = None
remoteCloseeNonce_opt = None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -445,22 +445,34 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers {
when(WAIT_FOR_DUAL_FUNDING_CONFIRMED)(handleExceptions {
case Event(txSigs: TxSignatures, d: DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED) =>
d.latestFundingTx.sharedTx match {
case fundingTx: PartiallySignedSharedTransaction => InteractiveTxSigningSession.addRemoteSigs(channelKeys, d.latestFundingTx.fundingParams, fundingTx, txSigs) match {
case Left(cause) =>
val unsignedFundingTx = fundingTx.tx.buildUnsignedTx()
log.warning("received invalid tx_signatures for txid={} (current funding txid={}): {}", txSigs.txId, unsignedFundingTx.txid, cause.getMessage)
// The funding transaction may still confirm (since our peer should be able to generate valid signatures),
// so we cannot close the channel yet.
stay() sending Error(d.channelId, InvalidFundingSignature(d.channelId, Some(unsignedFundingTx.txid)).getMessage)
case Right(fundingTx) =>
log.info("publishing funding tx for channelId={} fundingTxId={}", d.channelId, fundingTx.signedTx.txid)
val dfu1 = d.latestFundingTx.copy(sharedTx = fundingTx)
val d1 = d.copy(commitments = d.commitments.updateLocalFundingStatus(fundingTx.txId, dfu1, None) match {
case Left(commitments) => commitments
case Right((commitments, _)) => commitments
})
stay() using d1 storing() calling publishFundingTx(dfu1)
}
case fundingTx: PartiallySignedSharedTransaction =>
invalidTxSigsReceived.get(txSigs.txId) match {
case Some(count) if count >= 3 =>
// Our peer already sent us 3 invalid tx_signatures for that channel: they're either buggy or malicious,
// so we stop processing their additional tx_signatures and simply wait for the transaction to confirm.
log.warning("received too many invalid tx_signatures for fundingTxId={}, ignoring this one", txSigs.txId)
stay()
case _ =>
InteractiveTxSigningSession.addRemoteSigs(channelKeys, d.latestFundingTx.fundingParams, fundingTx, txSigs) match {
case Left(cause) =>
val unsignedFundingTx = fundingTx.tx.buildUnsignedTx()
log.warning("received invalid tx_signatures for txid={} (current funding txid={}): {}", txSigs.txId, unsignedFundingTx.txid, cause.getMessage)
// The funding transaction may still confirm (since our peer should be able to generate valid signatures),
// so we cannot close the channel yet. We record that they sent an invalid signature though.
if (txSigs.txId == fundingTx.txId) {
invalidTxSigsReceived += (txSigs.txId -> (invalidTxSigsReceived.getOrElse(txSigs.txId, 0) + 1))
}
stay() sending Error(d.channelId, InvalidFundingSignature(d.channelId, Some(unsignedFundingTx.txid)).getMessage)
case Right(fundingTx) =>
log.info("publishing funding tx for channelId={} fundingTxId={}", d.channelId, fundingTx.signedTx.txid)
val dfu1 = d.latestFundingTx.copy(sharedTx = fundingTx)
val d1 = d.copy(commitments = d.commitments.updateLocalFundingStatus(fundingTx.txId, dfu1, None) match {
case Left(commitments) => commitments
case Right((commitments, _)) => commitments
})
stay() using d1 storing() calling publishFundingTx(dfu1)
}
}
case _: FullySignedSharedTransaction =>
d.status match {
case DualFundingStatus.RbfWaitingForSigs(signingSession) =>
Expand Down Expand Up @@ -729,6 +741,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers {
val shortIds = createShortIdAliases(d.channelId)
val channelReady = createChannelReady(shortIds, d.commitments)
d.deferred.foreach(self ! _)
invalidTxSigsReceived = Map.empty
goto(WAIT_FOR_DUAL_FUNDING_READY) using DATA_WAIT_FOR_DUAL_FUNDING_READY(commitments1, shortIds) storing() sending channelReady
case Left(_) => stay()
}
Expand All @@ -744,6 +757,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers {
case _: DualFundingStatus.RbfRequested | _: DualFundingStatus.RbfInProgress | _: DualFundingStatus.RbfWaitingForSigs => Seq(TxAbort(d.channelId, InvalidRbfTxConfirmed(d.channelId).getMessage), channelReady)
}
d.deferred.foreach(self ! _)
invalidTxSigsReceived = Map.empty
goto(WAIT_FOR_DUAL_FUNDING_READY) using DATA_WAIT_FOR_DUAL_FUNDING_READY(commitments1, shortIds) storing() sending toSend
case Left(_) => stay()
}
Expand Down
Loading