1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-02-22 22:25:26 +01:00

Better router/channel exception handling (#71)

* added specific channel exceptions
* added specific router exceptions
* retry payment when an error occurs at the first channel
This commit is contained in:
Pierre-Marie Padiou 2017-04-20 11:47:38 +02:00 committed by GitHub
parent 2eded652e5
commit da78ae5356
11 changed files with 147 additions and 108 deletions

View file

@ -1,6 +1,6 @@
package fr.acinq.eclair.channel
import akka.actor.{ActorRef, FSM, LoggingFSM, OneForOneStrategy, Props, SupervisorStrategy}
import akka.actor.{ActorRef, FSM, LoggingFSM, OneForOneStrategy, Props, Status, SupervisorStrategy}
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin._
import fr.acinq.eclair._
@ -413,7 +413,7 @@ class Channel(val nodeParams: NodeParams, remoteNodeId: PublicKey, blockchain: A
when(NORMAL)(handleExceptions {
case Event(c: CMD_ADD_HTLC, d: DATA_NORMAL) if d.commitments.unackedShutdown().isDefined =>
handleCommandError(sender, new RuntimeException("cannot send new htlcs, closing in progress"))
handleCommandError(sender, ClosingInProgress)
case Event(c@CMD_ADD_HTLC(amountMsat, rHash, expiry, route, downstream_opt, do_commit), d@DATA_NORMAL(commitments, _)) =>
Try(Commitments.sendAdd(commitments, c)) match {
@ -422,9 +422,9 @@ class Channel(val nodeParams: NodeParams, remoteNodeId: PublicKey, blockchain: A
relayer ! AddHtlcSucceeded(add, origin)
if (do_commit) self ! CMD_SIGN
handleCommandSuccess(sender, d.copy(commitments = commitments1))
case Success(Left((failure, errorMessage))) =>
case Success(Left((failure, error))) =>
relayer ! AddHtlcFailed(c, failure)
handleCommandError(sender, new RuntimeException(errorMessage))
handleCommandError(sender, error)
case Failure(cause) => handleCommandError(sender, cause)
}
@ -436,7 +436,7 @@ class Channel(val nodeParams: NodeParams, remoteNodeId: PublicKey, blockchain: A
case Event(c@CMD_FULFILL_HTLC(id, r, do_commit), d: DATA_NORMAL) =>
Try(Commitments.sendFulfill(d.commitments, c)) match {
case Success((commitments1, fulfill)) =>
case Success((commitments1, _)) =>
if (do_commit) self ! CMD_SIGN
handleCommandSuccess(sender, d.copy(commitments = commitments1))
case Failure(cause) => handleCommandError(sender, cause)
@ -453,7 +453,7 @@ class Channel(val nodeParams: NodeParams, remoteNodeId: PublicKey, blockchain: A
case Event(c@CMD_FAIL_HTLC(id, reason, do_commit), d: DATA_NORMAL) =>
Try(Commitments.sendFail(d.commitments, c, nodeParams.privateKey)) match {
case Success((commitments1, fail)) =>
case Success((commitments1, _)) =>
if (do_commit) self ! CMD_SIGN
handleCommandSuccess(sender, d.copy(commitments = commitments1))
case Failure(cause) => handleCommandError(sender, cause)
@ -461,7 +461,7 @@ class Channel(val nodeParams: NodeParams, remoteNodeId: PublicKey, blockchain: A
case Event(c@CMD_FAIL_MALFORMED_HTLC(id, onionHash, failureCode, do_commit), d: DATA_NORMAL) =>
Try(Commitments.sendFailMalformed(d.commitments, c)) match {
case Success((commitments1, fail)) =>
case Success((commitments1, _)) =>
if (do_commit) self ! CMD_SIGN
handleCommandSuccess(sender, d.copy(commitments = commitments1))
case Failure(cause) => handleCommandError(sender, cause)
@ -558,20 +558,20 @@ class Channel(val nodeParams: NodeParams, remoteNodeId: PublicKey, blockchain: A
case Event(CMD_CLOSE(localScriptPubKey_opt), d: DATA_NORMAL) =>
val localScriptPubKey = localScriptPubKey_opt.getOrElse(d.commitments.localParams.defaultFinalScriptPubKey)
if (d.commitments.unackedShutdown().isDefined)
handleCommandError(sender, new RuntimeException("closing already in progress"))
handleCommandError(sender, ClosingAlreadyInProgress)
else if (Commitments.localHasChanges(d.commitments))
// TODO: simplistic behavior, we could also sign-then-close
handleCommandError(sender, new RuntimeException("cannot close when there are pending changes"))
handleCommandError(sender, CannotCloseWithPendingChanges)
else if (!Closing.isValidFinalScriptPubkey(localScriptPubKey))
handleCommandError(sender, new RuntimeException("invalid final script"))
handleCommandError(sender, InvalidFinalScript)
else
handleCommandSuccess(sender, d.copy(commitments = d.commitments.copy(unackedMessages = d.commitments.unackedMessages :+ Shutdown(d.channelId, localScriptPubKey))))
case Event(Shutdown(_, _), d@DATA_NORMAL(commitments, _)) if commitments.remoteChanges.proposed.size > 0 =>
handleLocalError(new RuntimeException("it is illegal to send a shutdown while having unsigned changes"), d)
handleLocalError(CannotCloseWithPendingChanges, d)
case Event(remoteShutdown@Shutdown(_, remoteScriptPubKey), d@DATA_NORMAL(commitments, _)) =>
require(Closing.isValidFinalScriptPubkey(remoteScriptPubKey), "invalid final script")
if (!Closing.isValidFinalScriptPubkey(remoteScriptPubKey)) throw InvalidFinalScript
Try(d.commitments.unackedShutdown().map(s => (s, commitments)).getOrElse {
// first if we have pending changes, we need to commit them
val commitments2 = if (Commitments.localHasChanges(commitments)) {
@ -592,8 +592,7 @@ class Channel(val nodeParams: NodeParams, remoteNodeId: PublicKey, blockchain: A
}
case Event(CurrentBlockCount(count), d: DATA_NORMAL) if d.commitments.hasTimedoutOutgoingHtlcs(count) =>
// TODO: fail htlc in upstream channel?
handleLocalError(new RuntimeException(s"one or more htlcs timedout at blockheight=$count, closing the channel"), d)
handleLocalError(HtlcTimedout, d)
case Event(CurrentFeerate(feeratePerKw), d: DATA_NORMAL) =>
d.commitments.localParams.isFunder match {
@ -601,7 +600,7 @@ class Channel(val nodeParams: NodeParams, remoteNodeId: PublicKey, blockchain: A
self ! CMD_UPDATE_FEE(feeratePerKw, commit = true)
stay
case false if Helpers.isFeeDiffTooHigh(d.commitments.localCommit.spec.feeratePerKw, feeratePerKw, nodeParams.maxFeerateMismatch) =>
handleLocalError(new RuntimeException(s"local/remote feerates are too different: remoteFeeratePerKw=${d.commitments.localCommit.spec.feeratePerKw} localFeeratePerKw=$feeratePerKw"), d)
handleLocalError(FeerateTooDifferent(localFeeratePerKw = feeratePerKw, remoteFeeratePerKw = d.commitments.localCommit.spec.feeratePerKw), d)
case _ => stay
}
@ -787,7 +786,7 @@ class Channel(val nodeParams: NodeParams, remoteNodeId: PublicKey, blockchain: A
}
case Event(CurrentBlockCount(count), d: DATA_SHUTDOWN) if d.commitments.hasTimedoutOutgoingHtlcs(count) =>
handleLocalError(new RuntimeException(s"one or more htlcs timedout at blockheight=$count, closing the channel"), d)
handleLocalError(HtlcTimedout, d)
case Event(CurrentFeerate(feeratePerKw), d: DATA_SHUTDOWN) =>
d.commitments.localParams.isFunder match {
@ -795,7 +794,7 @@ class Channel(val nodeParams: NodeParams, remoteNodeId: PublicKey, blockchain: A
self ! CMD_UPDATE_FEE(feeratePerKw, commit = true)
stay
case false if Helpers.isFeeDiffTooHigh(d.commitments.localCommit.spec.feeratePerKw, feeratePerKw, nodeParams.maxFeerateMismatch) =>
handleLocalError(new RuntimeException(s"local/remote feerates are too different: remoteFeeratePerKw=${d.commitments.localCommit.spec.feeratePerKw} localFeeratePerKw=$feeratePerKw"), d)
handleLocalError(FeerateTooDifferent(localFeeratePerKw = feeratePerKw, remoteFeeratePerKw = d.commitments.localCommit.spec.feeratePerKw), d)
case _ => stay
}
@ -833,7 +832,7 @@ class Channel(val nodeParams: NodeParams, remoteNodeId: PublicKey, blockchain: A
}
case Failure(cause) =>
log.error(cause, "cannot verify their close signature")
throw new RuntimeException("cannot verify their close signature", cause)
throw InvalidCloseSignature
}
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_NEGOTIATING) if tx.txid == Closing.makeClosingTx(d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, Satoshi(d.localClosingSigned.feeSatoshis))._1.tx.txid =>
@ -936,26 +935,25 @@ class Channel(val nodeParams: NodeParams, remoteNodeId: PublicKey, blockchain: A
relayer ! AddHtlcSucceeded(add, origin)
sender ! "ok"
goto(stateName) using d.copy(commitments = commitments1)
case Success(Left((failure, errorMessage))) =>
case Success(Left((failure, error))) =>
relayer ! AddHtlcFailed(c, failure)
handleCommandError(sender, new RuntimeException(errorMessage))
handleCommandError(sender, error)
case Failure(cause) => handleCommandError(sender, cause)
}
case Event(c@CMD_FULFILL_HTLC(id, r, do_commit), d: DATA_NORMAL) =>
log.info(s"we are disconnected so we just include the fulfill in our commitments")
Try(Commitments.sendFulfill(d.commitments, c)) match {
case Success((commitments1, fulfill)) =>
case Success((commitments1, _)) =>
sender ! "ok"
goto(stateName) using d.copy(commitments = commitments1)
case Failure(cause) => handleCommandError(sender, cause)
}
case Event(CMD_CLOSE(_), d: HasCommitments) => handleLocalError(new RuntimeException("can't do a mutual close while disconnected, doing an unilateral close instead"), d)
case Event(CMD_CLOSE(_), d: HasCommitments) => handleLocalError(ForcedLocalCommit("can't do a mutual close while disconnected"), d)
case Event(CurrentBlockCount(count), d: HasCommitments) if d.commitments.hasTimedoutOutgoingHtlcs(count) =>
// TODO: fail htlc in upstream channel?
handleLocalError(new RuntimeException(s"one or more htlcs timed out at blockheight=$count, closing the channel"), d)
handleLocalError(HtlcTimedout, d)
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: HasCommitments) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
@ -972,7 +970,7 @@ class Channel(val nodeParams: NodeParams, remoteNodeId: PublicKey, blockchain: A
whenUnhandled {
case Event(INPUT_PUBLISH_LOCALCOMMIT, d: HasCommitments) => handleLocalError(new RuntimeException(s"initiating local commit"), d)
case Event(INPUT_PUBLISH_LOCALCOMMIT, d: HasCommitments) => handleLocalError(ForcedLocalCommit("manual unilateral close"), d)
case Event(INPUT_DISCONNECTED, _) => goto(OFFLINE)
@ -1026,8 +1024,11 @@ class Channel(val nodeParams: NodeParams, remoteNodeId: PublicKey, blockchain: A
}
def handleCommandError(sender: ActorRef, cause: Throwable) = {
log.error(cause, "")
sender ! cause.getMessage
cause match {
case _: ChannelException => log.error(s"$cause")
case _ => log.error(cause, "")
}
sender ! Status.Failure(cause)
goto(stateName)
}

View file

@ -0,0 +1,33 @@
package fr.acinq.eclair.channel
/**
* Created by PM on 11/04/2017.
*/
class ChannelException(message: String) extends RuntimeException(message)
case object DebugTriggeredException extends ChannelException("debug-mode triggered failure")
case object ClosingInProgress extends ChannelException("cannot send new htlcs, closing in progress")
case object ClosingAlreadyInProgress extends ChannelException("closing already in progress")
case object CannotCloseWithPendingChanges extends ChannelException("cannot close when there are pending changes")
case object InvalidFinalScript extends ChannelException("invalid final script")
case object HtlcTimedout extends ChannelException(s"one or more htlcs timed out")
case class FeerateTooDifferent(localFeeratePerKw: Long, remoteFeeratePerKw: Long) extends ChannelException(s"local/remote feerates are too different: remoteFeeratePerKw=$remoteFeeratePerKw localFeeratePerKw=$localFeeratePerKw")
case object InvalidCloseSignature extends ChannelException("cannot verify their close signature")
case object InvalidCommitmentSignature extends ChannelException("invalid commitment signature")
case class ForcedLocalCommit(reason: String) extends ChannelException(s"forced local commit: reason")
case class UnexpectedHtlcId(expected: Long, actual: Long) extends ChannelException(s"unexpected htlc id: expected=$expected actual=$actual")
case class ExpiryTooSmall(minimum: Long, actual: Long, blockCount: Long) extends ChannelException(s"expiry too small: required=$minimum actual=$actual blockCount=$blockCount")
case class ExpiryCannotBeInThePast(expiry: Long, blockCount: Long) extends ChannelException(s"expiry can't be in the past: expiry=$expiry blockCount=$blockCount")
case class HtlcValueTooSmall(minimum: Long, actual: Long) extends ChannelException(s"htlc value too small: mininmum=$minimum actual=$actual")
case class HtlcValueTooHighInFlight(maximum: Long, actual: Long) extends ChannelException(s"in-flight htlcs hold too much value: maximum=$maximum actual=$actual")
case class TooManyAcceptedHtlcs(maximum: Long) extends ChannelException(s"too many accepted htlcs: maximum=$maximum")
case class InsufficientFunds(amountMsat: Long, missingSatoshis: Long, reserveSatoshis: Long, feesSatoshis: Long) extends ChannelException(s"insufficient funds: missingSatoshis=$missingSatoshis reserveSatoshis=$reserveSatoshis fees=$feesSatoshis")
case class InvalidHtlcPreimage(id: Long) extends ChannelException(s"invalid htlc preimage for htlc id=$id")
case class UnknownHtlcId(id: Long) extends ChannelException(s"unknown htlc id=$id")
case object FundeeCannotSendUpdateFee extends ChannelException(s"only the funder should send update_fee messages")
case class CannotAffordFees(missingSatoshis: Long, reserveSatoshis: Long, feesSatoshis: Long) extends ChannelException(s"can't pay the fee: missingSatoshis=$missingSatoshis reserveSatoshis=$reserveSatoshis feesSatoshis=$feesSatoshis")
case object CannotSignWithoutChanges extends ChannelException("cannot sign when there are no changes")
case object CannotSignBeforeRevocation extends ChannelException("cannot sign until next revocation hash is received")
case object UnexpectedRevocation extends ChannelException("received unexpected RevokeAndAck message")
case object InvalidRevocation extends ChannelException("invalid revocation")

View file

@ -76,18 +76,18 @@ object Commitments extends Logging {
* @param cmd add HTLC command
* @return either Left(failure, error message) where failure is a failure message (see BOLT #4 and the Failure Message class) or Right((new commitments, updateAddHtlc)
*/
def sendAdd(commitments: Commitments, cmd: CMD_ADD_HTLC): Either[(FailureMessage, String), (Commitments, UpdateAddHtlc)] = {
def sendAdd(commitments: Commitments, cmd: CMD_ADD_HTLC): Either[(FailureMessage, Throwable), (Commitments, UpdateAddHtlc)] = {
if (System.getProperty("failhtlc") == "yes") {
return Left(IncorrectPaymentAmount -> "debug-mode triggered failure")
return Left(IncorrectPaymentAmount -> DebugTriggeredException)
}
val blockCount = Globals.blockCount.get()
if (cmd.expiry <= blockCount) {
return Left(FinalExpiryTooSoon -> s"expiry can't be in the past (expiry=${cmd.expiry} blockCount=$blockCount)")
return Left(FinalExpiryTooSoon -> ExpiryCannotBeInThePast(cmd.expiry, blockCount))
}
if (cmd.amountMsat < commitments.remoteParams.htlcMinimumMsat) {
return Left(PermanentChannelFailure -> s"counterparty requires a minimum htlc value of ${commitments.remoteParams.htlcMinimumMsat} msat")
return Left(PermanentChannelFailure -> HtlcValueTooSmall(minimum = commitments.remoteParams.htlcMinimumMsat, actual = cmd.amountMsat))
}
// let's compute the current commitment *as seen by them* with this change taken into account
@ -98,13 +98,13 @@ object Commitments extends Logging {
val htlcValueInFlight = reduced.htlcs.map(_.add.amountMsat).sum
if (htlcValueInFlight > commitments1.remoteParams.maxHtlcValueInFlightMsat) {
// TODO: this should be a specific UPDATE error
return Left(TemporaryChannelFailure -> s"reached counterparty's in-flight htlcs value limit: value=$htlcValueInFlight max=${commitments1.remoteParams.maxHtlcValueInFlightMsat}")
return Left(TemporaryChannelFailure -> HtlcValueTooHighInFlight(maximum = commitments1.remoteParams.maxHtlcValueInFlightMsat, actual = htlcValueInFlight))
}
// the HTLC we are about to create is outgoing, but from their point of view it is incoming
val acceptedHtlcs = reduced.htlcs.count(_.direction == IN)
if (acceptedHtlcs > commitments1.remoteParams.maxAcceptedHtlcs) {
return Left(TemporaryChannelFailure -> s"reached counterparty's max accepted htlc count limit: value=$acceptedHtlcs max=${commitments1.remoteParams.maxAcceptedHtlcs}")
return Left(TemporaryChannelFailure -> TooManyAcceptedHtlcs(maximum = commitments1.remoteParams.maxAcceptedHtlcs))
}
// a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee
@ -112,7 +112,7 @@ object Commitments extends Logging {
val fees = if (commitments1.localParams.isFunder) Transactions.commitTxFee(Satoshi(commitments1.remoteParams.dustLimitSatoshis), reduced).amount else 0
val missing = reduced.toRemoteMsat / 1000 - commitments1.remoteParams.channelReserveSatoshis - fees
if (missing < 0) {
return Left(TemporaryChannelFailure -> s"insufficient funds: missing=${-1 * missing} reserve=${commitments1.remoteParams.channelReserveSatoshis} fees=$fees")
return Left(TemporaryChannelFailure -> InsufficientFunds(amountMsat = cmd.amountMsat, missingSatoshis = -1 * missing, reserveSatoshis = commitments1.remoteParams.channelReserveSatoshis, feesSatoshis = fees))
}
Right(commitments1, add)
@ -128,18 +128,18 @@ object Commitments extends Logging {
case false =>
if (add.id != commitments.remoteNextHtlcId) {
throw new RuntimeException(s"unexpected htlc id: actual=${add.id} expected=${commitments.remoteNextHtlcId}")
throw UnexpectedHtlcId(expected = commitments.remoteNextHtlcId, actual = add.id)
}
val blockCount = Globals.blockCount.get()
// if we are the final payee, we need a reasonable amount of time to pull the funds before the sender can get refunded
val minExpiry = blockCount + 3
if (add.expiry < minExpiry) {
throw new RuntimeException(s"expiry too small: required=$minExpiry actual=${add.expiry} (blockCount=$blockCount)")
throw ExpiryTooSmall(minimum = minExpiry, actual = add.expiry, blockCount = blockCount)
}
if (add.amountMsat < commitments.localParams.htlcMinimumMsat) {
throw new RuntimeException(s"htlc value too small: min=${commitments.localParams.htlcMinimumMsat}")
throw HtlcValueTooSmall(minimum = commitments.localParams.htlcMinimumMsat, actual = add.amountMsat)
}
// let's compute the current commitment *as seen by us* including this change
@ -148,19 +148,19 @@ object Commitments extends Logging {
val htlcValueInFlight = reduced.htlcs.map(_.add.amountMsat).sum
if (htlcValueInFlight > commitments1.localParams.maxHtlcValueInFlightMsat) {
throw new RuntimeException(s"in-flight htlcs hold too much value: value=$htlcValueInFlight max=${commitments1.localParams.maxHtlcValueInFlightMsat}")
throw HtlcValueTooHighInFlight(maximum = commitments1.localParams.maxHtlcValueInFlightMsat, actual = htlcValueInFlight)
}
val acceptedHtlcs = reduced.htlcs.count(_.direction == IN)
if (acceptedHtlcs > commitments1.localParams.maxAcceptedHtlcs) {
throw new RuntimeException(s"too many accepted htlcs: value=$acceptedHtlcs max=${commitments1.localParams.maxAcceptedHtlcs}")
throw TooManyAcceptedHtlcs(maximum = commitments1.localParams.maxAcceptedHtlcs)
}
// a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee
val fees = if (commitments1.localParams.isFunder) 0 else Transactions.commitTxFee(Satoshi(commitments1.localParams.dustLimitSatoshis), reduced).amount
val missing = reduced.toRemoteMsat / 1000 - commitments1.localParams.channelReserveSatoshis - fees
if (missing < 0) {
throw new RuntimeException(s"insufficient funds: missing=${-1 * missing} reserve=${commitments1.localParams.channelReserveSatoshis} fees=$fees")
throw InsufficientFunds(amountMsat = add.amountMsat, missingSatoshis = -1 * missing, reserveSatoshis = commitments1.localParams.channelReserveSatoshis, feesSatoshis = fees)
}
commitments1
@ -185,8 +185,8 @@ object Commitments extends Logging {
val fulfill = UpdateFulfillHtlc(commitments.channelId, cmd.id, cmd.r)
val commitments1 = addLocalProposal(commitments, fulfill)
(commitments1, fulfill)
case Some(htlc) => throw new RuntimeException(s"invalid htlc preimage for htlc id=${cmd.id}")
case None => throw new RuntimeException(s"unknown htlc id=${cmd.id}")
case Some(htlc) => throw InvalidHtlcPreimage(cmd.id)
case None => throw UnknownHtlcId(cmd.id)
}
def isOldFulfill(commitments: Commitments, fulfill: UpdateFulfillHtlc): Boolean =
@ -199,8 +199,8 @@ object Commitments extends Logging {
case true => Left(commitments)
case false => getHtlcCrossSigned(commitments, OUT, fulfill.id) match {
case Some(htlc) if htlc.paymentHash == sha256(fulfill.paymentPreimage) => Right(addRemoteProposal(commitments, fulfill))
case Some(htlc) => throw new RuntimeException(s"invalid htlc preimage for htlc id=${fulfill.id}")
case None => throw new RuntimeException(s"unknown htlc id=${fulfill.id}")
case Some(htlc) => throw InvalidHtlcPreimage(fulfill.id)
case None => throw UnknownHtlcId(fulfill.id)
}
}
@ -216,7 +216,7 @@ object Commitments extends Logging {
val fail = UpdateFailHtlc(commitments.channelId, cmd.id, reason)
val commitments1 = addLocalProposal(commitments, fail)
(commitments1, fail)
case None => throw new RuntimeException(s"unknown htlc id=${cmd.id}")
case None => throw UnknownHtlcId(cmd.id)
}
def sendFailMalformed(commitments: Commitments, cmd: CMD_FAIL_MALFORMED_HTLC): (Commitments, UpdateFailMalformedHtlc) =
@ -225,7 +225,7 @@ object Commitments extends Logging {
val fail = UpdateFailMalformedHtlc(commitments.channelId, cmd.id, cmd.onionHash, cmd.failureCode)
val commitments1 = addLocalProposal(commitments, fail)
(commitments1, fail)
case None => throw new RuntimeException(s"unknown htlc id=${cmd.id}")
case None => throw UnknownHtlcId(cmd.id)
}
def isOldFail(commitments: Commitments, fail: UpdateFailHtlc): Boolean =
@ -243,7 +243,7 @@ object Commitments extends Logging {
case true => Left(commitments)
case false => getHtlcCrossSigned(commitments, OUT, fail.id) match {
case Some(htlc) => Right(addRemoteProposal(commitments, fail))
case None => throw new RuntimeException(s"unknown htlc id=${fail.id}")
case None => throw UnknownHtlcId(fail.id)
}
}
@ -252,13 +252,13 @@ object Commitments extends Logging {
case true => Left(commitments)
case false => getHtlcCrossSigned(commitments, OUT, fail.id) match {
case Some(htlc) => Right(addRemoteProposal(commitments, fail))
case None => throw new RuntimeException(s"unknown htlc id=${fail.id}")
case None => throw UnknownHtlcId(fail.id)
}
}
def sendFee(commitments: Commitments, cmd: CMD_UPDATE_FEE): (Commitments, UpdateFee) = {
if (!commitments.localParams.isFunder) {
throw new RuntimeException(s"only the funder should send update_fee messages")
throw FundeeCannotSendUpdateFee
}
// let's compute the current commitment *as seen by them* with this change taken into account
val fee = UpdateFee(commitments.channelId, cmd.feeratePerKw)
@ -270,7 +270,7 @@ object Commitments extends Logging {
val fees = Transactions.commitTxFee(Satoshi(commitments1.remoteParams.dustLimitSatoshis), reduced).amount
val missing = reduced.toRemoteMsat / 1000 - commitments1.remoteParams.channelReserveSatoshis - fees
if (missing < 0) {
throw new RuntimeException(s"can't pay the fee: missing=${-1 * missing} reserve=${commitments1.localParams.channelReserveSatoshis} fees=$fees")
throw CannotAffordFees(missingSatoshis = -1 * missing, reserveSatoshis = commitments1.localParams.channelReserveSatoshis, feesSatoshis = fees)
}
(commitments1, fee)
@ -286,12 +286,12 @@ object Commitments extends Logging {
case true => commitments
case false =>
if (commitments.localParams.isFunder) {
throw new RuntimeException(s"only the funder should send update_fee messages")
throw FundeeCannotSendUpdateFee
}
val localFeeratePerKw = Globals.feeratePerKw.get()
if (Helpers.isFeeDiffTooHigh(fee.feeratePerKw, localFeeratePerKw, maxFeerateMismatch)) {
throw new RuntimeException(s"local/remote feerates are too different: remoteFeeratePerKw=${fee.feeratePerKw} localFeeratePerKw=$localFeeratePerKw")
throw FeerateTooDifferent(localFeeratePerKw = localFeeratePerKw, remoteFeeratePerKw = fee.feeratePerKw)
}
// NB: we check that the funder can afford this new fee even if spec allows to do it at next signature
@ -307,7 +307,7 @@ object Commitments extends Logging {
val fees = Transactions.commitTxFee(Satoshi(commitments1.remoteParams.dustLimitSatoshis), reduced).amount
val missing = reduced.toRemoteMsat / 1000 - commitments1.localParams.channelReserveSatoshis - fees
if (missing < 0) {
throw new RuntimeException(s"can't pay the fee: missing=${-1 * missing} reserve=${commitments1.localParams.channelReserveSatoshis} fees=$fees")
throw CannotAffordFees(missingSatoshis = -1 * missing, reserveSatoshis = commitments1.localParams.channelReserveSatoshis, feesSatoshis = fees)
}
commitments1
@ -325,7 +325,7 @@ object Commitments extends Logging {
import commitments._
commitments.remoteNextCommitInfo match {
case Right(_) if !localHasChanges(commitments) =>
throw new RuntimeException("cannot sign when there are no changes")
throw CannotSignWithoutChanges
case Right(remoteNextPerCommitmentPoint) =>
// remote commitment will includes all local changes + remote acked changes
val spec = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed)
@ -350,7 +350,7 @@ object Commitments extends Logging {
unackedMessages = unackedMessages :+ commitSig)
(commitments1, commitSig)
case Left(_) =>
throw new RuntimeException("cannot sign until next revocation hash is received")
throw CannotSignBeforeRevocation
}
}
@ -371,7 +371,7 @@ object Commitments extends Logging {
// and will increment our index
if (!remoteHasChanges(commitments))
throw new RuntimeException("cannot sign when there are no changes")
throw CannotSignWithoutChanges
// check that their signature is valid
// signatures are now optional in the commit message, and will be sent only if the other party is actually
@ -387,7 +387,7 @@ object Commitments extends Logging {
// no need to compute htlc sigs if commit sig doesn't check out
val signedCommitTx = Transactions.addSigs(localCommitTx, localParams.fundingPrivKey.publicKey, remoteParams.fundingPubKey, sig, commit.signature)
if (Transactions.checkSpendable(signedCommitTx).isFailure) {
throw new RuntimeException("invalid sig")
throw InvalidCommitmentSignature
}
val sortedHtlcTxs: Seq[TransactionWithInputInfo] = (htlcTimeoutTxs ++ htlcSuccessTxs).sortBy(_.input.outPoint.index)
@ -452,7 +452,7 @@ object Commitments extends Logging {
// we receive a revocation because we just sent them a sig for their next commit tx
remoteNextCommitInfo match {
case Left(_) if revocation.perCommitmentSecret.toPoint != remoteCommit.remotePerCommitmentPoint =>
throw new RuntimeException("invalid preimage")
throw InvalidRevocation
case Left(WaitingForRevocation(theirNextCommit, _, _)) =>
// they have received our last commitsig (otherwise they wouldn't have replied with a revocation)
// so we can acknowledge all our previous updates and the commitsig
@ -468,7 +468,7 @@ object Commitments extends Logging {
Right(commitments1)
case Right(_) =>
throw new RuntimeException("received unexpected RevokeAndAck message")
throw UnexpectedRevocation
}
}

View file

@ -62,10 +62,6 @@ class PaymentLifecycle(sourceNodeId: PublicKey, router: ActorRef, register: Acto
when(WAITING_FOR_PAYMENT_COMPLETE) {
case Event("ok", _) => stay()
case Event(reason: String, w: WaitingForComplete) =>
w.sender ! Status.Failure(new RuntimeException(reason))
stop(FSM.Failure(reason))
case Event(fulfill: UpdateFulfillHtlc, w: WaitingForComplete) =>
w.sender ! PaymentSucceeded(fulfill.paymentPreimage)
context.system.eventStream.publish(PaymentSent(MilliSatoshi(w.c.amountMsat), MilliSatoshi(w.cmd.amountMsat - w.c.amountMsat), w.cmd.paymentHash))
@ -107,10 +103,16 @@ class PaymentLifecycle(sourceNodeId: PublicKey, router: ActorRef, register: Acto
stop(FSM.Normal)
}
case Event(failure: Failure, w: WaitingForComplete) => {
w.sender ! failure
stop(FSM.Failure(failure.cause))
}
case Event(failure@Failure(cause), WaitingForComplete(s, c, _, attempts, _, ignoreNodes, ignoreChannels, hops)) =>
if (attempts < c.maxAttempts) {
log.info(s"received an error message from local, trying to use a different channel (failure=${cause.getMessage})")
router ! RouteRequest(sourceNodeId, c.targetNodeId, ignoreNodes, ignoreChannels + hops.head.lastUpdate.shortChannelId)
goto(WAITING_FOR_ROUTE) using WaitingForRoute(s, c, attempts)
} else {
s ! failure
stop(FSM.Failure(failure.cause))
}
}
}

View file

@ -284,7 +284,6 @@ object Router {
def findRouteDijkstra(localNodeId: BinaryData, targetNodeId: BinaryData, channels: Iterable[ChannelDesc]): Seq[ChannelDesc] = {
if (localNodeId == targetNodeId) throw CannotRouteToSelf
if (!channels.exists(c => c.a == localNodeId || c.b == localNodeId)) throw NoLocalChannels
case class DescEdge(desc: ChannelDesc) extends DefaultEdge
val g = new DefaultDirectedGraph[BinaryData, DescEdge](classOf[DescEdge])
channels.foreach(d => {
@ -298,10 +297,6 @@ object Router {
}
}
object NoLocalChannels extends RuntimeException("No local channels")
object RouteNotFound extends RuntimeException("Route not found")
object CannotRouteToSelf extends RuntimeException("Cannot route to self")
def findRoute(localNodeId: BinaryData, targetNodeId: BinaryData, updates: Map[ChannelDesc, ChannelUpdate])(implicit ec: ExecutionContext): Future[Seq[Hop]] = Future {
findRouteDijkstra(localNodeId, targetNodeId, updates.keys)
.map(desc => Hop(desc.a, desc.b, updates(desc)))

View file

@ -0,0 +1,10 @@
package fr.acinq.eclair.router
/**
* Created by PM on 12/04/2017.
*/
class RouterException(message: String) extends RuntimeException(message)
object RouteNotFound extends RouterException("Route not found")
object CannotRouteToSelf extends RouterException("Cannot route to self")

View file

@ -1,5 +1,6 @@
package fr.acinq.eclair.channel.states.e
import akka.actor.Status.Failure
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.bitcoin.Crypto.Scalar
import fr.acinq.bitcoin.{BinaryData, Crypto, Satoshi, ScriptFlags, Transaction}
@ -94,7 +95,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val sender = TestProbe()
val add = CMD_ADD_HTLC(500000000, "11" * 32, expiry = 300000)
sender.send(alice, add)
sender.expectMsg("expiry can't be in the past (expiry=300000 blockCount=400000)")
sender.expectMsg(Failure(ExpiryCannotBeInThePast(300000, 400000)))
relayer.expectMsg(AddHtlcFailed(add, FinalExpiryTooSoon))
alice2bob.expectNoMsg(200 millis)
}
@ -105,7 +106,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val sender = TestProbe()
val add = CMD_ADD_HTLC(50, "11" * 32, 400144)
sender.send(alice, add)
sender.expectMsg("counterparty requires a minimum htlc value of 1000 msat")
sender.expectMsg(Failure(HtlcValueTooSmall(1000, 50)))
relayer.expectMsg(AddHtlcFailed(add, PermanentChannelFailure))
alice2bob.expectNoMsg(200 millis)
}
@ -116,7 +117,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val sender = TestProbe()
val add = CMD_ADD_HTLC(Int.MaxValue, "11" * 32, 400144)
sender.send(alice, add)
sender.expectMsg("insufficient funds: missing=1376443 reserve=20000 fees=8960")
sender.expectMsg(Failure(InsufficientFunds(amountMsat = Int.MaxValue, missingSatoshis = 1376443, reserveSatoshis = 20000, feesSatoshis = 8960)))
relayer.expectMsg(AddHtlcFailed(add, TemporaryChannelFailure))
alice2bob.expectNoMsg(200 millis)
}
@ -139,7 +140,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
alice2bob.expectMsgType[UpdateAddHtlc]
val add = CMD_ADD_HTLC(1000000, "44" * 32, 400144)
sender.send(alice, add)
sender.expectMsg("insufficient funds: missing=1000 reserve=20000 fees=12400")
sender.expectMsg(Failure(InsufficientFunds(amountMsat = 1000000, missingSatoshis = 1000, reserveSatoshis = 20000, feesSatoshis = 12400)))
relayer.expectMsg(AddHtlcFailed(add, TemporaryChannelFailure))
alice2bob.expectNoMsg(200 millis)
}
@ -158,7 +159,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
alice2bob.expectMsgType[UpdateAddHtlc]
val add = CMD_ADD_HTLC(500000000, "33" * 32, 400144)
sender.send(alice, add)
sender.expectMsg("insufficient funds: missing=332400 reserve=20000 fees=12400")
sender.expectMsg(Failure(InsufficientFunds(amountMsat = 500000000, missingSatoshis = 332400, reserveSatoshis = 20000, feesSatoshis = 12400)))
relayer.expectMsg(AddHtlcFailed(add, TemporaryChannelFailure))
alice2bob.expectNoMsg(200 millis)
}
@ -169,7 +170,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val sender = TestProbe()
val add = CMD_ADD_HTLC(151000000, "11" * 32, 400144)
sender.send(bob, add)
sender.expectMsg("reached counterparty's in-flight htlcs value limit: value=151000000 max=150000000")
sender.expectMsg(Failure(HtlcValueTooHighInFlight(maximum = 150000000, actual = 151000000)))
relayer.expectMsg(AddHtlcFailed(add, TemporaryChannelFailure))
bob2alice.expectNoMsg(200 millis)
}
@ -187,7 +188,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
}
val add = CMD_ADD_HTLC(10000000, "33" * 32, 400144)
sender.send(alice, add)
sender.expectMsg("reached counterparty's max accepted htlc count limit: value=31 max=30")
sender.expectMsg(Failure(TooManyAcceptedHtlcs(maximum = 30)))
relayer.expectMsg(AddHtlcFailed(add, TemporaryChannelFailure))
alice2bob.expectNoMsg(200 millis)
}
@ -203,7 +204,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
// actual test starts here
sender.send(alice, CMD_ADD_HTLC(300000000, "11" * 32, 400144))
sender.expectMsg("cannot send new htlcs, closing in progress")
sender.expectMsg(Failure(ClosingInProgress))
}
}
@ -226,7 +227,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
bob ! htlc.copy(id = 3)
bob ! htlc.copy(id = 42)
val error = bob2alice.expectMsgType[Error]
assert(new String(error.data) === "unexpected htlc id: actual=42 expected=4")
assert(new String(error.data) === UnexpectedHtlcId(expected = 4, actual = 42).getMessage)
awaitCond(bob.stateName == CLOSING)
bob2blockchain.expectMsg(PublishAsap(tx))
bob2blockchain.expectMsgType[WatchConfirmed]
@ -239,7 +240,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val htlc = UpdateAddHtlc("00" * 32, 0, 150000, expiry = 1, BinaryData("00112233445566778899aabbccddeeff"), "")
alice2bob.forward(bob, htlc)
val error = bob2alice.expectMsgType[Error]
assert(new String(error.data) === "expiry too small: required=400003 actual=1 (blockCount=400000)")
assert(new String(error.data) === ExpiryTooSmall(minimum = 400003, actual = 1, blockCount = 400000).getMessage)
awaitCond(bob.stateName == CLOSING)
bob2blockchain.expectMsg(PublishAsap(tx))
bob2blockchain.expectMsgType[WatchConfirmed]
@ -252,7 +253,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val htlc = UpdateAddHtlc("00" * 32, 0, 150, expiry = 400144, BinaryData("00112233445566778899aabbccddeeff"), "")
alice2bob.forward(bob, htlc)
val error = bob2alice.expectMsgType[Error]
assert(new String(error.data) === "htlc value too small: min=1000")
assert(new String(error.data) === HtlcValueTooSmall(minimum = 1000, actual = 150).getMessage)
awaitCond(bob.stateName == CLOSING)
bob2blockchain.expectMsg(PublishAsap(tx))
bob2blockchain.expectMsgType[WatchConfirmed]
@ -265,7 +266,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val htlc = UpdateAddHtlc("00" * 32, 0, Long.MaxValue, 400144, BinaryData("00112233445566778899aabbccddeeff"), "")
alice2bob.forward(bob, htlc)
val error = bob2alice.expectMsgType[Error]
assert(new String(error.data) === "insufficient funds: missing=9223372036083735 reserve=20000 fees=8960")
assert(new String(error.data) === InsufficientFunds(amountMsat = Long.MaxValue, missingSatoshis = 9223372036083735L, reserveSatoshis = 20000, feesSatoshis = 8960).getMessage)
awaitCond(bob.stateName == CLOSING)
bob2blockchain.expectMsg(PublishAsap(tx))
bob2blockchain.expectMsgType[WatchConfirmed]
@ -280,7 +281,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
alice2bob.forward(bob, UpdateAddHtlc("00" * 32, 2, 167600000, 400144, "33" * 32, ""))
alice2bob.forward(bob, UpdateAddHtlc("00" * 32, 3, 10000000, 400144, "44" * 32, ""))
val error = bob2alice.expectMsgType[Error]
assert(new String(error.data) === "insufficient funds: missing=11720 reserve=20000 fees=14120")
assert(new String(error.data) === InsufficientFunds(amountMsat = 10000000, missingSatoshis = 11720, reserveSatoshis = 20000, feesSatoshis = 14120).getMessage)
awaitCond(bob.stateName == CLOSING)
bob2blockchain.expectMsg(PublishAsap(tx))
bob2blockchain.expectMsgType[WatchConfirmed]
@ -294,7 +295,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
alice2bob.forward(bob, UpdateAddHtlc("00" * 32, 1, 300000000, 400144, "22" * 32, ""))
alice2bob.forward(bob, UpdateAddHtlc("00" * 32, 2, 500000000, 400144, "33" * 32, ""))
val error = bob2alice.expectMsgType[Error]
assert(new String(error.data) === "insufficient funds: missing=332400 reserve=20000 fees=12400")
assert(new String(error.data) === InsufficientFunds(amountMsat = 500000000, missingSatoshis = 332400, reserveSatoshis = 20000, feesSatoshis = 12400).getMessage)
awaitCond(bob.stateName == CLOSING)
bob2blockchain.expectMsg(PublishAsap(tx))
bob2blockchain.expectMsgType[WatchConfirmed]
@ -306,7 +307,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
alice2bob.forward(alice, UpdateAddHtlc("00" * 32, 0, 151000000, 400144, "11" * 32, ""))
val error = alice2bob.expectMsgType[Error]
assert(new String(error.data) === "in-flight htlcs hold too much value: value=151000000 max=150000000")
assert(new String(error.data) === HtlcValueTooHighInFlight(maximum = 150000000, actual = 151000000).getMessage)
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(PublishAsap(tx))
alice2blockchain.expectMsgType[WatchConfirmed]
@ -322,7 +323,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
}
alice2bob.forward(bob, UpdateAddHtlc("00" * 32, 30, 1000000, 400144, "11" * 32, ""))
val error = bob2alice.expectMsgType[Error]
assert(new String(error.data) === "too many accepted htlcs: value=31 max=30")
assert(new String(error.data) === TooManyAcceptedHtlcs(maximum = 30).getMessage)
awaitCond(bob.stateName == CLOSING)
bob2blockchain.expectMsg(PublishAsap(tx))
bob2blockchain.expectMsgType[WatchConfirmed]
@ -689,7 +690,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
sender.send(bob, CMD_FULFILL_HTLC(42, r))
sender.expectMsg("unknown htlc id=42")
sender.expectMsg(Failure(UnknownHtlcId(42)))
assert(initialState == bob.stateData)
}
}
@ -703,7 +704,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
// actual test begins
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
sender.send(bob, CMD_FULFILL_HTLC(htlc.id, "00" * 32))
sender.expectMsg("invalid htlc preimage for htlc id=0")
sender.expectMsg(Failure(InvalidHtlcPreimage(0)))
assert(initialState == bob.stateData)
}
}
@ -814,7 +815,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
sender.send(bob, CMD_FAIL_HTLC(42, Right(PermanentChannelFailure)))
sender.expectMsg("unknown htlc id=42")
sender.expectMsg(Failure(UnknownHtlcId(42)))
assert(initialState == bob.stateData)
}
}
@ -904,7 +905,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val sender = TestProbe()
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
sender.send(bob, CMD_UPDATE_FEE(20000))
sender.expectMsg("only the funder should send update_fee messages")
sender.expectMsg(Failure(FundeeCannotSendUpdateFee))
assert(initialState == bob.stateData)
}
}
@ -939,7 +940,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
Globals.feeratePerKw.set(fee.feeratePerKw)
sender.send(bob, fee)
val error = bob2alice.expectMsgType[Error]
assert(new String(error.data) === "can't pay the fee: missing=71620000 reserve=20000 fees=72400000")
assert(new String(error.data) === CannotAffordFees(missingSatoshis = 71620000L, reserveSatoshis = 20000L, feesSatoshis=72400000L).getMessage)
awaitCond(bob.stateName == CLOSING)
bob2blockchain.expectMsg(PublishAsap(tx))
bob2blockchain.expectMsgType[WatchConfirmed]
@ -976,7 +977,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val sender = TestProbe()
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
sender.send(alice, CMD_CLOSE(None))
sender.expectMsg("cannot close when there are pending changes")
sender.expectMsg(Failure(CannotCloseWithPendingChanges))
}
}
@ -984,7 +985,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
within(30 seconds) {
val sender = TestProbe()
sender.send(alice, CMD_CLOSE(Some(BinaryData("00112233445566778899"))))
sender.expectMsg("invalid final script")
sender.expectMsg(Failure(InvalidFinalScript))
}
}
@ -1011,7 +1012,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
awaitCond(alice.stateName == NORMAL)
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.unackedShutdown.isDefined)
sender.send(alice, CMD_CLOSE(None))
sender.expectMsg("closing already in progress")
sender.expectMsg(Failure(ClosingAlreadyInProgress))
}
}

View file

@ -1,5 +1,6 @@
package fr.acinq.eclair.channel.states.f
import akka.actor.Status.Failure
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.bitcoin.Crypto.Scalar
import fr.acinq.bitcoin.{BinaryData, Crypto, Satoshi, ScriptFlags, Transaction}
@ -90,7 +91,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val sender = TestProbe()
val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN]
sender.send(bob, CMD_FULFILL_HTLC(42, "12" * 32))
sender.expectMsg("unknown htlc id=42")
sender.expectMsg(Failure(UnknownHtlcId(42)))
assert(initialState == bob.stateData)
}
}
@ -100,7 +101,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val sender = TestProbe()
val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN]
sender.send(bob, CMD_FULFILL_HTLC(1, "00" * 32))
sender.expectMsg("invalid htlc preimage for htlc id=1")
sender.expectMsg(Failure(InvalidHtlcPreimage(1)))
assert(initialState == bob.stateData)
}
}
@ -173,7 +174,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val sender = TestProbe()
val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN]
sender.send(bob, CMD_FAIL_HTLC(42, Right(PermanentChannelFailure)))
sender.expectMsg("unknown htlc id=42")
sender.expectMsg(Failure(UnknownHtlcId(42)))
assert(initialState == bob.stateData)
}
}
@ -393,7 +394,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val sender = TestProbe()
val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN]
sender.send(bob, CMD_UPDATE_FEE(20000))
sender.expectMsg("only the funder should send update_fee messages")
sender.expectMsg(Failure(FundeeCannotSendUpdateFee))
assert(initialState == bob.stateData)
}
}
@ -428,7 +429,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
Globals.feeratePerKw.set(fee.feeratePerKw)
sender.send(bob, fee)
val error = bob2alice.expectMsgType[Error]
assert(new String(error.data) === "can't pay the fee: missing=72120000 reserve=20000 fees=72400000")
assert(new String(error.data) === CannotAffordFees(missingSatoshis = 72120000L, reserveSatoshis = 20000L, feesSatoshis=72400000L).getMessage)
awaitCond(bob.stateName == CLOSING)
bob2blockchain.expectMsg(PublishAsap(tx))
bob2blockchain.expectMsgType[WatchConfirmed]

View file

@ -6,8 +6,7 @@ import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.bitcoin.MilliSatoshi
import fr.acinq.eclair.Globals
import fr.acinq.eclair.crypto.Sphinx
import fr.acinq.eclair.router.BaseRouterSpec
import fr.acinq.eclair.router.Router.RouteNotFound
import fr.acinq.eclair.router.{BaseRouterSpec, RouteNotFound}
import fr.acinq.eclair.wire.{TemporaryChannelFailure, UpdateFailHtlc, UpdateFulfillHtlc}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner

View file

@ -2,7 +2,6 @@ package fr.acinq.eclair.router
import fr.acinq.bitcoin.Crypto.PrivateKey
import fr.acinq.bitcoin.{BinaryData, Crypto}
import fr.acinq.eclair.router.Router.{CannotRouteToSelf, NoLocalChannels, RouteNotFound}
import fr.acinq.eclair.wire.ChannelUpdate
import org.junit.runner.RunWith
import org.scalatest.FunSuite
@ -10,7 +9,6 @@ import org.scalatest.junit.JUnitRunner
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.util.{Failure, Try}
/**
* Created by PM on 31/05/2016.
@ -44,7 +42,7 @@ class RouteCalculationSpec extends FunSuite {
val exc = intercept[RuntimeException] {
Router.findRouteDijkstra(a, e, channels)
}
assert(exc == NoLocalChannels)
assert(exc == RouteNotFound)
}
test("route not found") {

View file

@ -6,7 +6,6 @@ import fr.acinq.bitcoin.Script.{pay2wsh, write}
import fr.acinq.bitcoin.{Satoshi, Transaction, TxOut}
import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.channel.BITCOIN_FUNDING_OTHER_CHANNEL_SPENT
import fr.acinq.eclair.router.Router.{NoLocalChannels, RouteNotFound}
import fr.acinq.eclair.transactions.Scripts
import fr.acinq.eclair.wire.Error
import fr.acinq.eclair.{randomKey, toShortId}
@ -113,7 +112,7 @@ class RouterSpec extends BaseRouterSpec {
val sender = TestProbe()
// no route a->f
sender.send(router, RouteRequest(randomKey.publicKey, f))
sender.expectMsg(Failure(NoLocalChannels))
sender.expectMsg(Failure(RouteNotFound))
}
test("route not found (non-existing target)") { case (router, _) =>