Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

htlc post restart 4 #1586

Merged
merged 19 commits into from
Nov 9, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
37 changes: 16 additions & 21 deletions eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,8 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
}

case Event(c: CloseCommand, d) =>
val channelId = Helpers.getChannelId(d)
channelOpenReplyToUser(Right(ChannelOpenResponse.ChannelClosed(channelId)))
handleFastClose(c, channelId)
channelOpenReplyToUser(Right(ChannelOpenResponse.ChannelClosed(d.channelId)))
handleFastClose(c, d.channelId)

case Event(TickChannelOpenTimeout, _) =>
channelOpenReplyToUser(Left(LocalError(new RuntimeException("open channel cancelled, took too long"))))
Expand Down Expand Up @@ -334,9 +333,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
goto(WAIT_FOR_FUNDING_CREATED) using DATA_WAIT_FOR_FUNDING_CREATED(open.temporaryChannelId, localParams, remoteParams, open.fundingSatoshis, open.pushMsat, open.feeratePerKw, open.firstPerCommitmentPoint, open.channelFlags, channelVersion, accept) sending accept
}

case Event(c: CloseCommand, d) =>
val channelId = Helpers.getChannelId(d)
handleFastClose(c, channelId)
case Event(c: CloseCommand, d) => handleFastClose(c, d.channelId)

case Event(e: Error, d: DATA_WAIT_FOR_OPEN_CHANNEL) => handleRemoteError(e, d)

Expand Down Expand Up @@ -1666,27 +1663,26 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId

case Event(c: CMD_GETINFO, _) =>
val replyTo = if (c.replyTo == ActorRef.noSender) sender else c.replyTo
val channelId = Helpers.getChannelId(stateData)
replyTo ! RES_GETINFO(remoteNodeId, channelId, stateName, stateData)
replyTo ! RES_GETINFO(remoteNodeId, stateData.channelId, stateName, stateData)
stay

case Event(c: CMD_ADD_HTLC, d: HasCommitments) =>
log.info(s"rejecting htlc request in state=$stateName")
val error = ChannelUnavailable(d.channelId)
handleAddHtlcCommandError(c, error, None) // we don't provide a channel_update: this will be a permanent channel failure

case Event(c: CMD_CLOSE, d) => handleCommandError(CommandUnavailableInThisState(Helpers.getChannelId(d), "close", stateName), c)
case Event(c: CMD_CLOSE, d) => handleCommandError(CommandUnavailableInThisState(d.channelId, "close", stateName), c)

case Event(c: CMD_FORCECLOSE, d) =>
d match {
case data: HasCommitments =>
val replyTo = if (c.replyTo == ActorRef.noSender) sender else c.replyTo
replyTo ! RES_SUCCESS(c, data.channelId)
handleLocalError(ForcedLocalCommit(data.channelId), data, Some(c))
case _ => handleCommandError(CommandUnavailableInThisState(Helpers.getChannelId(d), "forceclose", stateName), c)
case _ => handleCommandError(CommandUnavailableInThisState(d.channelId, "forceclose", stateName), c)
}

case Event(c: CMD_UPDATE_RELAY_FEE, d) => handleCommandError(CommandUnavailableInThisState(Helpers.getChannelId(d), "updaterelayfee", stateName), c)
case Event(c: CMD_UPDATE_RELAY_FEE, d) => handleCommandError(CommandUnavailableInThisState(d.channelId, "updaterelayfee", stateName), c)

// we only care about this event in NORMAL and SHUTDOWN state, and there may be cases where the task is not cancelled
case Event(_: RevocationTimeout, _) => stay
Expand Down Expand Up @@ -1879,8 +1875,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
case hasReplyTo: HasReplyToCommand => if (hasReplyTo.replyTo == ActorRef.noSender) Some(sender) else Some(hasReplyTo.replyTo)
}
replyTo_opt.foreach { replyTo =>
val channelId = Helpers.getChannelId(newData)
replyTo ! RES_SUCCESS(c, channelId)
replyTo ! RES_SUCCESS(c, newData.channelId)
}
stay using newData
}
Expand All @@ -1889,7 +1884,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
log.warning(s"${cause.getMessage} while processing cmd=${c.getClass.getSimpleName} in state=$stateName")
val replyTo = if (c.replyTo == ActorRef.noSender) sender else c.replyTo
replyTo ! RES_ADD_FAILED(c, cause, channelUpdate)
context.system.eventStream.publish(ChannelErrorOccurred(self, Helpers.getChannelId(stateData), remoteNodeId, stateData, LocalError(cause), isFatal = false))
context.system.eventStream.publish(ChannelErrorOccurred(self, stateData.channelId, remoteNodeId, stateData, LocalError(cause), isFatal = false))
stay
}

Expand All @@ -1906,7 +1901,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
replyTo_opt.foreach { replyTo =>
replyTo ! RES_FAILURE(c, cause)
}
context.system.eventStream.publish(ChannelErrorOccurred(self, Helpers.getChannelId(stateData), remoteNodeId, stateData, LocalError(cause), isFatal = false))
context.system.eventStream.publish(ChannelErrorOccurred(self, stateData.channelId, remoteNodeId, stateData, LocalError(cause), isFatal = false))
stay
}

Expand Down Expand Up @@ -1959,15 +1954,15 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
val error = Error(d.channelId, exc.getMessage)
// NB: we don't use the handleLocalError handler because it would result in the commit tx being published, which we don't want:
// implementation *guarantees* that in case of BITCOIN_FUNDING_PUBLISH_FAILED, the funding tx hasn't and will never be published, so we can close the channel right away
context.system.eventStream.publish(ChannelErrorOccurred(self, Helpers.getChannelId(stateData), remoteNodeId, stateData, LocalError(exc), isFatal = true))
context.system.eventStream.publish(ChannelErrorOccurred(self, stateData.channelId, remoteNodeId, stateData, LocalError(exc), isFatal = true))
goto(CLOSED) sending error
}

def handleFundingTimeout(d: HasCommitments) = {
log.warning(s"funding tx hasn't been confirmed in time, cancelling channel delay=$FUNDING_TIMEOUT_FUNDEE")
val exc = FundingTxTimedout(d.channelId)
val error = Error(d.channelId, exc.getMessage)
context.system.eventStream.publish(ChannelErrorOccurred(self, Helpers.getChannelId(stateData), remoteNodeId, stateData, LocalError(exc), isFatal = true))
context.system.eventStream.publish(ChannelErrorOccurred(self, stateData.channelId, remoteNodeId, stateData, LocalError(exc), isFatal = true))
goto(CLOSED) sending error
}

Expand Down Expand Up @@ -2038,8 +2033,8 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
case _: ChannelException => ()
case _ => log.error(cause, s"msg=${msg.getOrElse("n/a")} stateData=$stateData ")
}
val error = Error(Helpers.getChannelId(d), cause.getMessage)
context.system.eventStream.publish(ChannelErrorOccurred(self, Helpers.getChannelId(stateData), remoteNodeId, stateData, LocalError(cause), isFatal = true))
val error = Error(d.channelId, cause.getMessage)
context.system.eventStream.publish(ChannelErrorOccurred(self, stateData.channelId, remoteNodeId, stateData, LocalError(cause), isFatal = true))

d match {
case dd: HasCommitments if Closing.nothingAtStake(dd) => goto(CLOSED)
Expand All @@ -2055,7 +2050,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
def handleRemoteError(e: Error, d: Data) = {
// see BOLT 1: only print out data verbatim if is composed of printable ASCII characters
log.error(s"peer sent error: ascii='${e.toAscii}' bin=${e.data.toHex}")
context.system.eventStream.publish(ChannelErrorOccurred(self, Helpers.getChannelId(stateData), remoteNodeId, stateData, RemoteError(e), isFatal = true))
context.system.eventStream.publish(ChannelErrorOccurred(self, stateData.channelId, remoteNodeId, stateData, RemoteError(e), isFatal = true))

d match {
case _: DATA_CLOSING => stay // nothing to do, there is already a spending tx published
Expand Down Expand Up @@ -2405,7 +2400,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
val category_opt = LogCategory(currentMessage)
val id = currentMessage match {
case INPUT_RESTORED(data) => data.channelId
case _ => Helpers.getChannelId(stateData)
case _ => stateData.channelId
}
Logs.mdc(category_opt, remoteNodeId_opt = Some(remoteNodeId), channelId_opt = Some(id))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,13 +244,15 @@ object ChannelOpenResponse {
8888888P" d88P 888 888 d88P 888
*/

sealed trait Data
sealed trait Data {
def channelId: ByteVector32 = ByteVector32.Zeroes
akumaigorodski marked this conversation as resolved.
Show resolved Hide resolved
}

case object Nothing extends Data

sealed trait HasCommitments extends Data {
override val channelId: ByteVector32 = commitments.channelId
def commitments: Commitments
def channelId: ByteVector32 = commitments.channelId
}

case class ClosingTxProposed(unsignedTx: Transaction, localClosingSigned: ClosingSigned)
Expand All @@ -259,11 +261,19 @@ case class LocalCommitPublished(commitTx: Transaction, claimMainDelayedOutputTx:
case class RemoteCommitPublished(commitTx: Transaction, claimMainOutputTx: Option[Transaction], claimHtlcSuccessTxs: List[Transaction], claimHtlcTimeoutTxs: List[Transaction], irrevocablySpent: Map[OutPoint, ByteVector32])
case class RevokedCommitPublished(commitTx: Transaction, claimMainOutputTx: Option[Transaction], mainPenaltyTx: Option[Transaction], htlcPenaltyTxs: List[Transaction], claimHtlcDelayedPenaltyTxs: List[Transaction], irrevocablySpent: Map[OutPoint, ByteVector32])

final case class DATA_WAIT_FOR_OPEN_CHANNEL(initFundee: INPUT_INIT_FUNDEE) extends Data
final case class DATA_WAIT_FOR_ACCEPT_CHANNEL(initFunder: INPUT_INIT_FUNDER, lastSent: OpenChannel) extends Data
final case class DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId: ByteVector32, localParams: LocalParams, remoteParams: RemoteParams, fundingAmount: Satoshi, pushAmount: MilliSatoshi, initialFeeratePerKw: FeeratePerKw, remoteFirstPerCommitmentPoint: PublicKey, channelVersion: ChannelVersion, lastSent: OpenChannel) extends Data
final case class DATA_WAIT_FOR_FUNDING_CREATED(temporaryChannelId: ByteVector32, localParams: LocalParams, remoteParams: RemoteParams, fundingAmount: Satoshi, pushAmount: MilliSatoshi, initialFeeratePerKw: FeeratePerKw, remoteFirstPerCommitmentPoint: PublicKey, channelFlags: Byte, channelVersion: ChannelVersion, lastSent: AcceptChannel) extends Data
final case class DATA_WAIT_FOR_FUNDING_SIGNED(channelId: ByteVector32, localParams: LocalParams, remoteParams: RemoteParams, fundingTx: Transaction, fundingTxFee: Satoshi, localSpec: CommitmentSpec, localCommitTx: CommitTx, remoteCommit: RemoteCommit, channelFlags: Byte, channelVersion: ChannelVersion, lastSent: FundingCreated) extends Data
final case class DATA_WAIT_FOR_OPEN_CHANNEL(initFundee: INPUT_INIT_FUNDEE) extends Data {
override val channelId: ByteVector32 = initFundee.temporaryChannelId
}
final case class DATA_WAIT_FOR_ACCEPT_CHANNEL(initFunder: INPUT_INIT_FUNDER, lastSent: OpenChannel) extends Data {
override val channelId: ByteVector32 = initFunder.temporaryChannelId
}
final case class DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId: ByteVector32, localParams: LocalParams, remoteParams: RemoteParams, fundingAmount: Satoshi, pushAmount: MilliSatoshi, initialFeeratePerKw: FeeratePerKw, remoteFirstPerCommitmentPoint: PublicKey, channelVersion: ChannelVersion, lastSent: OpenChannel) extends Data {
override val channelId: ByteVector32 = temporaryChannelId
}
final case class DATA_WAIT_FOR_FUNDING_CREATED(temporaryChannelId: ByteVector32, localParams: LocalParams, remoteParams: RemoteParams, fundingAmount: Satoshi, pushAmount: MilliSatoshi, initialFeeratePerKw: FeeratePerKw, remoteFirstPerCommitmentPoint: PublicKey, channelFlags: Byte, channelVersion: ChannelVersion, lastSent: AcceptChannel) extends Data {
override val channelId: ByteVector32 = temporaryChannelId
}
final case class DATA_WAIT_FOR_FUNDING_SIGNED(override val channelId: ByteVector32, localParams: LocalParams, remoteParams: RemoteParams, fundingTx: Transaction, fundingTxFee: Satoshi, localSpec: CommitmentSpec, localCommitTx: CommitTx, remoteCommit: RemoteCommit, channelFlags: Byte, channelVersion: ChannelVersion, lastSent: FundingCreated) extends Data
final case class DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments: Commitments,
fundingTx: Option[Transaction],
waitingSince: Long, // how long have we been waiting for the funding tx to confirm
Expand Down
18 changes: 1 addition & 17 deletions eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import fr.acinq.eclair.transactions.Scripts._
import fr.acinq.eclair.transactions.Transactions._
import fr.acinq.eclair.transactions._
import fr.acinq.eclair.wire._
import fr.acinq.eclair.{NodeParams, ShortChannelId, addressToPublicKeyScript, _}
import fr.acinq.eclair._
import scodec.bits.ByteVector

import scala.concurrent.Await
Expand All @@ -44,22 +44,6 @@ import scala.util.{Failure, Success, Try}
*/

object Helpers {

/**
* Depending on the state, returns the current temporaryChannelId or channelId
*
* @return the long identifier of the channel
*/
def getChannelId(stateData: Data): ByteVector32 = stateData match {
t-bast marked this conversation as resolved.
Show resolved Hide resolved
case Nothing => ByteVector32.Zeroes
case d: DATA_WAIT_FOR_OPEN_CHANNEL => d.initFundee.temporaryChannelId
case d: DATA_WAIT_FOR_ACCEPT_CHANNEL => d.initFunder.temporaryChannelId
case d: DATA_WAIT_FOR_FUNDING_INTERNAL => d.temporaryChannelId
case d: DATA_WAIT_FOR_FUNDING_CREATED => d.temporaryChannelId
case d: DATA_WAIT_FOR_FUNDING_SIGNED => d.channelId
case d: HasCommitments => d.channelId
}

/**
* We update local/global features at reconnection
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ trait StateTestsHelperMethods extends TestKitBase with FixtureTestSuite with Par
getRemoteCommitPublished(s.stateData.asInstanceOf[DATA_CLOSING]).get
}

def channelId(a: TestFSMRef[State, Data, Channel]): ByteVector32 = Helpers.getChannelId(a.stateData)
def channelId(a: TestFSMRef[State, Data, Channel]): ByteVector32 = a.stateData.channelId

// @formatter:off
implicit class ChannelWithTestFeeConf(a: TestFSMRef[State, Data, Channel]) {
Expand Down