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:
parent
2eded652e5
commit
da78ae5356
11 changed files with 147 additions and 108 deletions
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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")
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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, _) =>
|
||||
|
|
Loading…
Add table
Reference in a new issue