mirror of
https://github.com/ACINQ/eclair.git
synced 2025-03-14 03:48:13 +01:00
Merge branch 'master' into wip-android
This commit is contained in:
commit
4afc498226
33 changed files with 873 additions and 487 deletions
6
.dockerignore
Normal file
6
.dockerignore
Normal file
|
@ -0,0 +1,6 @@
|
|||
Dockerfile
|
||||
.dockerignore
|
||||
.git
|
||||
**/*.idea
|
||||
**/*.iml
|
||||
**/target
|
49
Dockerfile
Normal file
49
Dockerfile
Normal file
|
@ -0,0 +1,49 @@
|
|||
FROM openjdk:8u121-jdk-alpine as BUILD
|
||||
|
||||
# Setup maven, we don't use https://hub.docker.com/_/maven/ as it declare .m2 as volume, we loose all mvn cache
|
||||
# We can alternatively do as proposed by https://github.com/carlossg/docker-maven#packaging-a-local-repository-with-the-image
|
||||
# this was meant to make the image smaller, but we use multi-stage build so we don't care
|
||||
|
||||
RUN apk add --no-cache curl tar bash
|
||||
|
||||
ARG MAVEN_VERSION=3.5.2
|
||||
ARG USER_HOME_DIR="/root"
|
||||
ARG SHA=707b1f6e390a65bde4af4cdaf2a24d45fc19a6ded00fff02e91626e3e42ceaff
|
||||
ARG BASE_URL=https://apache.osuosl.org/maven/maven-3/${MAVEN_VERSION}/binaries
|
||||
|
||||
RUN mkdir -p /usr/share/maven /usr/share/maven/ref \
|
||||
&& curl -fsSL -o /tmp/apache-maven.tar.gz ${BASE_URL}/apache-maven-${MAVEN_VERSION}-bin.tar.gz \
|
||||
&& echo "${SHA} /tmp/apache-maven.tar.gz" | sha256sum -c - \
|
||||
&& tar -xzf /tmp/apache-maven.tar.gz -C /usr/share/maven --strip-components=1 \
|
||||
&& rm -f /tmp/apache-maven.tar.gz \
|
||||
&& ln -s /usr/share/maven/bin/mvn /usr/bin/mvn
|
||||
|
||||
ENV MAVEN_HOME /usr/share/maven
|
||||
ENV MAVEN_CONFIG "$USER_HOME_DIR/.m2"
|
||||
|
||||
# Let's fetch eclair dependencies, so that Docker can cache them
|
||||
# This way we won't have to fetch dependencies again if only the source code changes
|
||||
# The easiest way to reliably get dependencies is to build the project with no sources
|
||||
WORKDIR /usr/src
|
||||
COPY pom.xml pom.xml
|
||||
COPY eclair-core/pom.xml eclair-core/pom.xml
|
||||
COPY eclair-node/pom.xml eclair-node/pom.xml
|
||||
COPY eclair-node-gui/pom.xml eclair-node-gui/pom.xml
|
||||
RUN mkdir -p eclair-core/src/main/scala && touch eclair-core/src/main/scala/empty.scala
|
||||
# Blank build. We only care about eclair-node, and we use install because eclair-node depends on eclair-core
|
||||
RUN mvn install -pl eclair-node -am clean
|
||||
|
||||
# Only then do we copy the sources
|
||||
COPY . .
|
||||
|
||||
# And this time we can build in offline mode
|
||||
RUN mvn package -pl eclair-node -am -DskipTests -o
|
||||
# It might be good idea to run the tests here, so that the docker build fail if the code is bugged
|
||||
|
||||
# We currently use a debian image for runtime because of some jni-related issue with sqlite
|
||||
FROM openjdk:8u151-jre-slim
|
||||
WORKDIR /app
|
||||
# Eclair only needs the eclair-node-*.jar to run
|
||||
COPY --from=BUILD /usr/src/eclair-node/target/eclair-node-*.jar .
|
||||
RUN ln `ls` eclair-node
|
||||
ENTRYPOINT [ "java", "-jar", "eclair-node" ]
|
|
@ -15,17 +15,6 @@
|
|||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>pl.project13.maven</groupId>
|
||||
<artifactId>git-commit-id-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>revision</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.googlecode.maven-download-plugin</groupId>
|
||||
<artifactId>download-maven-plugin</artifactId>
|
||||
|
|
|
@ -52,7 +52,7 @@ eclair {
|
|||
default-feerate-per-kb = 20000 // default bitcoin core value
|
||||
|
||||
max-htlc-value-in-flight-msat = 100000000000 // 1 BTC ~= unlimited
|
||||
htlc-minimum-msat = 1000000
|
||||
htlc-minimum-msat = 10000
|
||||
max-accepted-htlcs = 30
|
||||
|
||||
reserve-to-funding-ratio = 0.01 // recommended by BOLT #2
|
||||
|
@ -63,7 +63,7 @@ eclair {
|
|||
expiry-delta-blocks = 144
|
||||
|
||||
fee-base-msat = 546000
|
||||
fee-proportional-millionth = 10
|
||||
fee-proportional-millionths = 10
|
||||
|
||||
// maximum local vs remote feerate mismatch; 1.0 means 100%
|
||||
// actual check is abs((local feerate - remote fee rate) / (local fee rate + remote fee rate)/2) > fee rate mismatch
|
||||
|
@ -81,21 +81,4 @@ eclair {
|
|||
auto-reconnect = true
|
||||
|
||||
payment-handler = "local"
|
||||
}
|
||||
akka {
|
||||
loggers = ["akka.event.slf4j.Slf4jLogger"]
|
||||
loglevel = "DEBUG"
|
||||
|
||||
actor {
|
||||
debug {
|
||||
# enable DEBUG logging of all LoggingFSMs for events, transitions and timers
|
||||
fsm = on
|
||||
}
|
||||
}
|
||||
|
||||
http {
|
||||
host-connection-pool {
|
||||
max-open-requests = 64
|
||||
}
|
||||
}
|
||||
}
|
|
@ -53,7 +53,9 @@ case class NodeParams(extendedPrivateKey: ExtendedPrivateKey,
|
|||
chainHash: BinaryData,
|
||||
channelFlags: Byte,
|
||||
channelExcludeDuration: FiniteDuration,
|
||||
watcherType: WatcherType)
|
||||
watcherType: WatcherType) {
|
||||
val nodeId = privateKey.publicKey
|
||||
}
|
||||
|
||||
object NodeParams {
|
||||
|
||||
|
@ -140,7 +142,7 @@ object NodeParams {
|
|||
minDepthBlocks = config.getInt("mindepth-blocks"),
|
||||
smartfeeNBlocks = 3,
|
||||
feeBaseMsat = config.getInt("fee-base-msat"),
|
||||
feeProportionalMillionth = config.getInt("fee-proportional-millionth"),
|
||||
feeProportionalMillionth = config.getInt("fee-proportional-millionths"),
|
||||
reserveToFundingRatio = config.getDouble("reserve-to-funding-ratio"),
|
||||
maxReserveToFundingRatio = config.getDouble("max-reserve-to-funding-ratio"),
|
||||
channelsDb = channelsDb,
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package fr.acinq.eclair.channel
|
||||
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
import akka.actor.{ActorRef, FSM, LoggingFSM, OneForOneStrategy, Props, Status, SupervisorStrategy}
|
||||
import akka.event.Logging.MDC
|
||||
import akka.pattern.pipe
|
||||
|
@ -12,7 +14,7 @@ import fr.acinq.eclair.blockchain._
|
|||
import fr.acinq.eclair.channel.Helpers.{Closing, Funding}
|
||||
import fr.acinq.eclair.crypto.{Generators, ShaChain, Sphinx}
|
||||
import fr.acinq.eclair.payment._
|
||||
import fr.acinq.eclair.router.{Announcements, TickBroadcast}
|
||||
import fr.acinq.eclair.router.Announcements
|
||||
import fr.acinq.eclair.transactions._
|
||||
import fr.acinq.eclair.wire.{ChannelReestablish, _}
|
||||
import org.bitcoinj.script.{Script => BitcoinjScript}
|
||||
|
@ -48,6 +50,9 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
|
||||
import Channel._
|
||||
|
||||
// we pass these to helpers classes so that they have the logging context
|
||||
implicit def implicitLog = log
|
||||
|
||||
val forwarder = context.actorOf(Props(new Forwarder(nodeParams)), "forwarder")
|
||||
|
||||
// this will be used to detect htlc timeouts
|
||||
|
@ -140,15 +145,19 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
// no need to go OFFLINE, we can directly switch to CLOSING
|
||||
goto(CLOSING) using closing
|
||||
|
||||
case d: HasCommitments =>
|
||||
d match {
|
||||
case DATA_NORMAL(_, Some(shortChannelId), _, _, _) =>
|
||||
context.system.eventStream.publish(ShortChannelIdAssigned(self, d.channelId, shortChannelId))
|
||||
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, shortChannelId, nodeParams.expiryDeltaBlocks, nodeParams.htlcMinimumMsat, nodeParams.feeBaseMsat, nodeParams.feeProportionalMillionth, enable = false)
|
||||
relayer ! channelUpdate
|
||||
case _ => ()
|
||||
case normal: DATA_NORMAL =>
|
||||
context.system.eventStream.publish(ShortChannelIdAssigned(self, normal.channelId, normal.channelUpdate.shortChannelId))
|
||||
// we rebuild a channel_update for two reasons:
|
||||
// - we want to reload values from configuration
|
||||
// - if eclair was previously killed, it might not have had time to publish a channel_update with enable=false
|
||||
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, normal.channelUpdate.shortChannelId, nodeParams.expiryDeltaBlocks, normal.commitments.remoteParams.htlcMinimumMsat, nodeParams.feeBaseMsat, nodeParams.feeProportionalMillionth, enable = false)
|
||||
if (normal.channelAnnouncement.isDefined) {
|
||||
router ! channelUpdate
|
||||
}
|
||||
goto(OFFLINE) using d
|
||||
relayer ! channelUpdate
|
||||
goto(OFFLINE) using normal.copy(channelUpdate = channelUpdate)
|
||||
|
||||
case _ => goto(OFFLINE) using data
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -199,7 +208,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
|
||||
case Event(CMD_CLOSE(_), _) => goto(CLOSED)
|
||||
|
||||
case Event(e: Error, _) => handleRemoteErrorNoCommitments(e)
|
||||
case Event(e: Error, d: DATA_WAIT_FOR_OPEN_CHANNEL) => handleRemoteError(e, d)
|
||||
|
||||
case Event(INPUT_DISCONNECTED, _) => goto(CLOSED)
|
||||
})
|
||||
|
@ -238,7 +247,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
|
||||
case Event(CMD_CLOSE(_), _) => goto(CLOSED)
|
||||
|
||||
case Event(e: Error, _) => handleRemoteErrorNoCommitments(e)
|
||||
case Event(e: Error, d: DATA_WAIT_FOR_ACCEPT_CHANNEL) => handleRemoteError(e, d)
|
||||
|
||||
case Event(INPUT_DISCONNECTED, _) => goto(CLOSED)
|
||||
})
|
||||
|
@ -263,12 +272,13 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
|
||||
case Event(Status.Failure(t), d: DATA_WAIT_FOR_FUNDING_INTERNAL) =>
|
||||
log.error(t, s"wallet returned error: ")
|
||||
val error = Error(d.temporaryChannelId, "aborting channel creation".getBytes)
|
||||
val exc = ChannelFundingError(d.temporaryChannelId)
|
||||
val error = Error(d.temporaryChannelId, exc.getMessage.getBytes)
|
||||
goto(CLOSED) sending error
|
||||
|
||||
case Event(CMD_CLOSE(_), _) => goto(CLOSED)
|
||||
|
||||
case Event(e: Error, _) => handleRemoteErrorNoCommitments(e)
|
||||
case Event(e: Error, d: DATA_WAIT_FOR_FUNDING_INTERNAL) => handleRemoteError(e, d)
|
||||
|
||||
case Event(INPUT_DISCONNECTED, _) => goto(CLOSED)
|
||||
})
|
||||
|
@ -284,7 +294,8 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
Transactions.checkSpendable(signedLocalCommitTx) match {
|
||||
case Failure(cause) =>
|
||||
log.error(cause, "their FundingCreated message contains an invalid signature")
|
||||
val error = Error(temporaryChannelId, cause.getMessage.getBytes)
|
||||
val exc = InvalidCommitmentSignature(temporaryChannelId, signedLocalCommitTx.tx)
|
||||
val error = Error(temporaryChannelId, exc.getMessage.getBytes)
|
||||
// we haven't anything at stake yet, we can just stop
|
||||
goto(CLOSED) sending error
|
||||
case Success(_) =>
|
||||
|
@ -314,7 +325,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
|
||||
case Event(CMD_CLOSE(_), _) => goto(CLOSED)
|
||||
|
||||
case Event(e: Error, _) => handleRemoteErrorNoCommitments(e)
|
||||
case Event(e: Error, d: DATA_WAIT_FOR_FUNDING_CREATED) => handleRemoteError(e, d)
|
||||
|
||||
case Event(INPUT_DISCONNECTED, _) => goto(CLOSED)
|
||||
})
|
||||
|
@ -324,11 +335,11 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
// we make sure that their sig checks out and that our first commit tx is spendable
|
||||
val localSigOfLocalTx = Transactions.sign(localCommitTx, localParams.fundingPrivKey)
|
||||
val signedLocalCommitTx = Transactions.addSigs(localCommitTx, localParams.fundingPrivKey.publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig)
|
||||
Transactions.checkSpendable(fundingTx, signedLocalCommitTx.tx)
|
||||
.flatMap(_ => Transactions.checkSpendable(signedLocalCommitTx)) match {
|
||||
Transactions.checkSpendable(signedLocalCommitTx) match {
|
||||
case Failure(cause) =>
|
||||
log.error(cause, "their FundingSigned message contains an invalid signature")
|
||||
val error = Error(channelId, cause.getMessage.getBytes)
|
||||
val exc = InvalidCommitmentSignature(channelId, signedLocalCommitTx.tx)
|
||||
val error = Error(channelId, exc.getMessage.getBytes)
|
||||
// we rollback the funding tx, it will never be published
|
||||
wallet.rollback(fundingTx)
|
||||
// we haven't published anything yet, we can just stop
|
||||
|
@ -365,7 +376,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
case Event(e: Error, d: DATA_WAIT_FOR_FUNDING_SIGNED) =>
|
||||
// we rollback the funding tx, it will never be published
|
||||
wallet.rollback(d.fundingTx)
|
||||
handleRemoteErrorNoCommitments(e)
|
||||
handleRemoteError(e, d)
|
||||
})
|
||||
|
||||
when(WAIT_FOR_FUNDING_CONFIRMED)(handleExceptions {
|
||||
|
@ -379,27 +390,34 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
val nextPerCommitmentPoint = Generators.perCommitPoint(commitments.localParams.shaSeed, 1)
|
||||
val fundingLocked = FundingLocked(commitments.channelId, nextPerCommitmentPoint)
|
||||
deferred.map(self ! _)
|
||||
goto(WAIT_FOR_FUNDING_LOCKED) using store(DATA_WAIT_FOR_FUNDING_LOCKED(commitments, fundingLocked)) sending fundingLocked
|
||||
// this is the temporary channel id that we will use in our channel_update message, the goal is to be able to use our channel
|
||||
// as soon as it reaches NORMAL state, and before it is announced on the network
|
||||
// (this id might be updated when the funding tx gets deeply buried, if there was a reorg in the meantime)
|
||||
val shortChannelId = toShortId(blockHeight, txIndex, commitments.commitInput.outPoint.index.toInt)
|
||||
goto(WAIT_FOR_FUNDING_LOCKED) using store(DATA_WAIT_FOR_FUNDING_LOCKED(commitments, shortChannelId, fundingLocked)) sending fundingLocked
|
||||
|
||||
case Event(BITCOIN_FUNDING_PUBLISH_FAILED, d: DATA_WAIT_FOR_FUNDING_CONFIRMED) =>
|
||||
val error = Error(d.channelId, "Funding tx publish failure".getBytes)
|
||||
log.error(s"failed to publish funding tx")
|
||||
val exc = ChannelFundingError(d.channelId)
|
||||
val error = Error(d.channelId, exc.getMessage.getBytes)
|
||||
goto(ERR_FUNDING_PUBLISH_FAILED) sending error
|
||||
|
||||
// TODO: not implemented, users will have to manually close
|
||||
case Event(BITCOIN_FUNDING_TIMEOUT, d: DATA_WAIT_FOR_FUNDING_CONFIRMED) =>
|
||||
val error = Error(d.channelId, "Funding tx timed out".getBytes)
|
||||
val exc = FundingTxTimedout(d.channelId)
|
||||
val error = Error(d.channelId, exc.getMessage.getBytes)
|
||||
goto(ERR_FUNDING_TIMEOUT) sending error
|
||||
|
||||
case Event(remoteAnnSigs: AnnouncementSignatures, d: DATA_WAIT_FOR_FUNDING_CONFIRMED) if d.commitments.announceChannel =>
|
||||
log.info(s"received remote announcement signatures, delaying")
|
||||
log.debug(s"received remote announcement signatures, delaying")
|
||||
// we may receive their announcement sigs before our watcher notifies us that the channel has reached min_conf (especially during testing when blocks are generated in bulk)
|
||||
// note: no need to persist their message, in case of disconnection they will resend it
|
||||
context.system.scheduler.scheduleOnce(2 seconds, self, remoteAnnSigs)
|
||||
stay
|
||||
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_WAIT_FOR_FUNDING_CONFIRMED) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx), d: DATA_WAIT_FOR_FUNDING_CONFIRMED) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
|
||||
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, _), d: DATA_WAIT_FOR_FUNDING_CONFIRMED) => handleInformationLeak(d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx), d: DATA_WAIT_FOR_FUNDING_CONFIRMED) => handleInformationLeak(tx, d)
|
||||
|
||||
case Event(CMD_CLOSE(_), d: DATA_WAIT_FOR_FUNDING_CONFIRMED) => spendLocalCurrent(d)
|
||||
|
||||
|
@ -407,29 +425,32 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
})
|
||||
|
||||
when(WAIT_FOR_FUNDING_LOCKED)(handleExceptions {
|
||||
case Event(FundingLocked(_, nextPerCommitmentPoint), d@DATA_WAIT_FOR_FUNDING_LOCKED(commitments, _)) =>
|
||||
if (d.commitments.announceChannel) {
|
||||
// used for announcement of channel (if minDepth >= ANNOUNCEMENTS_MINCONF this event will fire instantly)
|
||||
blockchain ! WatchConfirmed(self, commitments.commitInput.outPoint.txid, commitments.commitInput.txOut.publicKeyScript, ANNOUNCEMENTS_MINCONF, BITCOIN_FUNDING_DEEPLYBURIED)
|
||||
} else if (d.commitments.announceChannel && nodeParams.watcherType == BITCOINJ && d.commitments.localParams.isFunder && System.getProperty("spvtest") != null) {
|
||||
case Event(FundingLocked(_, nextPerCommitmentPoint), d@DATA_WAIT_FOR_FUNDING_LOCKED(commitments, shortChannelId, _)) =>
|
||||
if (d.commitments.announceChannel && nodeParams.watcherType == BITCOINJ && d.commitments.localParams.isFunder && System.getProperty("spvtest") != null) {
|
||||
// bitcoinj-based watcher currently can't get the tx index in block (which is used to calculate the short id)
|
||||
// instead, we rely on a hack by trusting the index the counterparty sends us
|
||||
// but in testing when connecting to bitcoinj impl together we make the funder choose some random data
|
||||
log.warning("using hardcoded short id for testing with bitcoinj!!!!!")
|
||||
context.system.scheduler.scheduleOnce(5 seconds, self, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, Random.nextInt(100), Random.nextInt(100)))
|
||||
} else {
|
||||
// used to get the final shortChannelId, used in announcements (if minDepth >= ANNOUNCEMENTS_MINCONF this event will fire instantly)
|
||||
blockchain ! WatchConfirmed(self, commitments.commitInput.outPoint.txid, commitments.commitInput.txOut.publicKeyScript, ANNOUNCEMENTS_MINCONF, BITCOIN_FUNDING_DEEPLYBURIED)
|
||||
}
|
||||
goto(NORMAL) using store(DATA_NORMAL(commitments.copy(remoteNextCommitInfo = Right(nextPerCommitmentPoint)), None, None, None, None))
|
||||
context.system.eventStream.publish(ShortChannelIdAssigned(self, commitments.channelId, shortChannelId))
|
||||
val initialChannelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, shortChannelId, nodeParams.expiryDeltaBlocks, d.commitments.remoteParams.htlcMinimumMsat, nodeParams.feeBaseMsat, nodeParams.feeProportionalMillionth, enable = true)
|
||||
relayer ! initialChannelUpdate
|
||||
goto(NORMAL) using store(DATA_NORMAL(commitments.copy(remoteNextCommitInfo = Right(nextPerCommitmentPoint)), shortChannelId, None, initialChannelUpdate, None, None, None))
|
||||
|
||||
case Event(remoteAnnSigs: AnnouncementSignatures, d: DATA_WAIT_FOR_FUNDING_LOCKED) if d.commitments.announceChannel =>
|
||||
log.info(s"received remote announcement signatures, delaying")
|
||||
log.debug(s"received remote announcement signatures, delaying")
|
||||
// we may receive their announcement sigs before our watcher notifies us that the channel has reached min_conf (especially during testing when blocks are generated in bulk)
|
||||
// note: no need to persist their message, in case of disconnection they will resend it
|
||||
context.system.scheduler.scheduleOnce(2 seconds, self, remoteAnnSigs)
|
||||
stay
|
||||
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_WAIT_FOR_FUNDING_LOCKED) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx), d: DATA_WAIT_FOR_FUNDING_LOCKED) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
|
||||
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, _), d: DATA_WAIT_FOR_FUNDING_LOCKED) => handleInformationLeak(d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx), d: DATA_WAIT_FOR_FUNDING_LOCKED) => handleInformationLeak(tx, d)
|
||||
|
||||
case Event(CMD_CLOSE(_), d: DATA_WAIT_FOR_FUNDING_LOCKED) => spendLocalCurrent(d)
|
||||
|
||||
|
@ -451,22 +472,22 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
case Event(c: CMD_ADD_HTLC, d: DATA_NORMAL) if d.localShutdown.isDefined || d.remoteShutdown.isDefined =>
|
||||
// note: spec would allow us to keep sending new htlcs after having received their shutdown (and not sent ours)
|
||||
// but we want to converge as fast as possible and they would probably not route them anyway
|
||||
val error = ClosingInProgress(d.channelId)
|
||||
handleCommandAddError(error, origin(c))
|
||||
val error = NoMoreHtlcsClosingInProgress(d.channelId)
|
||||
handleCommandError(AddHtlcFailed(d.channelId, error, origin(c), Some(d.channelUpdate)))
|
||||
|
||||
case Event(c: CMD_ADD_HTLC, d: DATA_NORMAL) =>
|
||||
Try(Commitments.sendAdd(d.commitments, c, origin(c))) match {
|
||||
case Success(Right((commitments1, add))) =>
|
||||
if (c.commit) self ! CMD_SIGN
|
||||
handleCommandSuccess(sender, d.copy(commitments = commitments1)) sending add
|
||||
case Success(Left(error)) => handleCommandAddError(error, origin(c))
|
||||
case Failure(cause) => handleCommandError(cause)
|
||||
case Success(Left(error)) => handleCommandError(AddHtlcFailed(d.channelId, error, origin(c), Some(d.channelUpdate)))
|
||||
case Failure(cause) => handleCommandError(AddHtlcFailed(d.channelId, cause, origin(c), Some(d.channelUpdate)))
|
||||
}
|
||||
|
||||
case Event(add: UpdateAddHtlc, d: DATA_NORMAL) =>
|
||||
Try(Commitments.receiveAdd(d.commitments, add)) match {
|
||||
case Success(commitments1) => stay using d.copy(commitments = commitments1)
|
||||
case Failure(cause) => handleLocalError(cause, d)
|
||||
case Failure(cause) => handleLocalError(cause, d, Some(add))
|
||||
}
|
||||
|
||||
case Event(c: CMD_FULFILL_HTLC, d: DATA_NORMAL) =>
|
||||
|
@ -483,7 +504,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
relayer ! ForwardFulfill(fulfill, origin)
|
||||
stay using d.copy(commitments = commitments1)
|
||||
case Success(Left(_)) => stay
|
||||
case Failure(cause) => handleLocalError(cause, d)
|
||||
case Failure(cause) => handleLocalError(cause, d, Some(fulfill))
|
||||
}
|
||||
|
||||
case Event(c: CMD_FAIL_HTLC, d: DATA_NORMAL) =>
|
||||
|
@ -508,7 +529,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
relayer ! ForwardFail(fail, origin)
|
||||
stay using d.copy(commitments = commitments1)
|
||||
case Success(Left(_)) => stay
|
||||
case Failure(cause) => handleLocalError(cause, d)
|
||||
case Failure(cause) => handleLocalError(cause, d, Some(fail))
|
||||
}
|
||||
|
||||
case Event(fail: UpdateFailMalformedHtlc, d: DATA_NORMAL) =>
|
||||
|
@ -517,7 +538,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
relayer ! ForwardFailMalformed(fail, origin)
|
||||
stay using d.copy(commitments = commitments1)
|
||||
case Success(Left(_)) => stay
|
||||
case Failure(cause) => handleLocalError(cause, d)
|
||||
case Failure(cause) => handleLocalError(cause, d, Some(fail))
|
||||
}
|
||||
|
||||
case Event(c: CMD_UPDATE_FEE, d: DATA_NORMAL) =>
|
||||
|
@ -531,7 +552,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
case Event(fee: UpdateFee, d: DATA_NORMAL) =>
|
||||
Try(Commitments.receiveFee(d.commitments, fee, nodeParams.maxFeerateMismatch)) match {
|
||||
case Success(commitments1) => stay using d.copy(commitments = commitments1)
|
||||
case Failure(cause) => handleLocalError(cause, d)
|
||||
case Failure(cause) => handleLocalError(cause, d, Some(fee))
|
||||
}
|
||||
|
||||
case Event(CMD_SIGN, d: DATA_NORMAL) =>
|
||||
|
@ -563,7 +584,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
}
|
||||
context.system.eventStream.publish(ChannelSignatureReceived(self, commitments1))
|
||||
stay using store(d.copy(commitments = commitments1)) sending revocation
|
||||
case Failure(cause) => handleLocalError(cause, d)
|
||||
case Failure(cause) => handleLocalError(cause, d, Some(commit))
|
||||
}
|
||||
|
||||
case Event(revocation: RevokeAndAck, d: DATA_NORMAL) =>
|
||||
|
@ -591,7 +612,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
} else {
|
||||
stay using store(d.copy(commitments = commitments1))
|
||||
}
|
||||
case Failure(cause) => handleLocalError(cause, d)
|
||||
case Failure(cause) => handleLocalError(cause, d, Some(revocation))
|
||||
}
|
||||
|
||||
case Event(CMD_CLOSE(localScriptPubKey_opt), d: DATA_NORMAL) =>
|
||||
|
@ -625,9 +646,9 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
// there are no htlcs => go to NEGOTIATING
|
||||
|
||||
if (!Closing.isValidFinalScriptPubkey(remoteScriptPubKey)) {
|
||||
handleLocalError(InvalidFinalScript(d.channelId), d)
|
||||
handleLocalError(InvalidFinalScript(d.channelId), d, Some(remoteShutdown))
|
||||
} else if (Commitments.remoteHasUnsignedOutgoingHtlcs(d.commitments)) {
|
||||
handleLocalError(CannotCloseWithUnsignedOutgoingHtlcs(d.channelId), d)
|
||||
handleLocalError(CannotCloseWithUnsignedOutgoingHtlcs(d.channelId), d, Some(remoteShutdown))
|
||||
} else if (Commitments.localHasUnsignedOutgoingHtlcs(d.commitments)) { // do we have unsigned outgoing htlcs?
|
||||
require(d.localShutdown.isEmpty, "can't have pending unsigned outgoing htlcs after having sent Shutdown")
|
||||
// are we in the middle of a signature?
|
||||
|
@ -657,64 +678,42 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
if (d.commitments.hasNoPendingHtlcs) {
|
||||
// there are no pending signed htlcs, let's go directly to NEGOTIATING
|
||||
val closingSigned = Closing.makeFirstClosingTx(d.commitments, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey)
|
||||
goto(NEGOTIATING) using store(DATA_NEGOTIATING(d.commitments, localShutdown, remoteShutdown, closingSigned)) sending sendList :+ closingSigned
|
||||
goto(NEGOTIATING) using store(DATA_NEGOTIATING(d.commitments, localShutdown, remoteShutdown, closingSigned :: Nil)) sending sendList :+ closingSigned
|
||||
} else {
|
||||
// there are some pending signed htlcs, we need to fail/fullfill them
|
||||
goto(SHUTDOWN) using store(DATA_SHUTDOWN(d.commitments, localShutdown, remoteShutdown)) sending sendList
|
||||
}
|
||||
}
|
||||
|
||||
case Event(CurrentBlockCount(count), d: DATA_NORMAL) if d.commitments.hasTimedoutOutgoingHtlcs(count) =>
|
||||
handleLocalError(HtlcTimedout(d.channelId), d)
|
||||
case Event(c@CurrentBlockCount(count), d: DATA_NORMAL) if d.commitments.hasTimedoutOutgoingHtlcs(count) =>
|
||||
handleLocalError(HtlcTimedout(d.channelId), d, Some(c))
|
||||
|
||||
case Event(CurrentFeerates(feeratesPerKw), d: DATA_NORMAL) =>
|
||||
case Event(c@CurrentFeerates(feeratesPerKw), d: DATA_NORMAL) =>
|
||||
val networkFeeratePerKw = feeratesPerKw.block_1
|
||||
d.commitments.localParams.isFunder match {
|
||||
case true if Helpers.shouldUpdateFee(d.commitments.localCommit.spec.feeratePerKw, networkFeeratePerKw, nodeParams.updateFeeMinDiffRatio) =>
|
||||
self ! CMD_UPDATE_FEE(networkFeeratePerKw, commit = true)
|
||||
stay
|
||||
case false if Helpers.isFeeDiffTooHigh(d.commitments.localCommit.spec.feeratePerKw, networkFeeratePerKw, nodeParams.maxFeerateMismatch) =>
|
||||
handleLocalError(FeerateTooDifferent(d.channelId, localFeeratePerKw = networkFeeratePerKw, remoteFeeratePerKw = d.commitments.localCommit.spec.feeratePerKw), d)
|
||||
handleLocalError(FeerateTooDifferent(d.channelId, localFeeratePerKw = networkFeeratePerKw, remoteFeeratePerKw = d.commitments.localCommit.spec.feeratePerKw), d, Some(c))
|
||||
case _ => stay
|
||||
}
|
||||
|
||||
case Event(WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, blockHeight, txIndex), d: DATA_NORMAL) if d.commitments.announceChannel && d.shortChannelId.isEmpty =>
|
||||
case Event(WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, blockHeight, txIndex), d: DATA_NORMAL) if d.channelAnnouncement.isEmpty =>
|
||||
val shortChannelId = toShortId(blockHeight, txIndex, d.commitments.commitInput.outPoint.index.toInt)
|
||||
log.info(s"funding tx is deeply buried at blockHeight=$blockHeight txIndex=$txIndex, sending announcements")
|
||||
val annSignatures = Helpers.makeAnnouncementSignatures(nodeParams, d.commitments, shortChannelId)
|
||||
stay using store(d.copy(localAnnouncementSignatures = Some(annSignatures))) sending annSignatures
|
||||
log.info(s"funding tx is deeply buried at blockHeight=$blockHeight txIndex=$txIndex shortChannelId=$shortChannelId")
|
||||
// we re-announce this shortChannelId, because it might be different from the one we were using before if there was a reorg
|
||||
context.system.eventStream.publish(ShortChannelIdAssigned(self, d.channelId, shortChannelId))
|
||||
val annSignatures_opt = if (d.commitments.announceChannel) Some(Helpers.makeAnnouncementSignatures(nodeParams, d.commitments, shortChannelId)) else None
|
||||
stay using store(d.copy(shortChannelId = shortChannelId, localAnnouncementSignatures = annSignatures_opt)) sending annSignatures_opt.toSeq
|
||||
|
||||
case Event(remoteAnnSigs: AnnouncementSignatures, d: DATA_NORMAL) if d.commitments.announceChannel =>
|
||||
// channels are publicly announced if both parties want it (defined as feature bit)
|
||||
d.localAnnouncementSignatures match {
|
||||
case Some(localAnnSigs) if d.shortChannelId.isDefined =>
|
||||
// this can happen if our announcement_signatures was lost during a disconnection
|
||||
// specs says that we "MUST respond to the first announcement_signatures message after reconnection with its own announcement_signatures message"
|
||||
// current implementation always replies to announcement_signatures, not only the first time
|
||||
log.info(s"re-sending our announcement sigs")
|
||||
stay sending localAnnSigs
|
||||
case Some(localAnnSigs) =>
|
||||
require(localAnnSigs.shortChannelId == remoteAnnSigs.shortChannelId, s"shortChannelId mismatch: local=${localAnnSigs.shortChannelId} remote=${remoteAnnSigs.shortChannelId}")
|
||||
log.info(s"announcing channelId=${d.channelId} on the network with shortId=${localAnnSigs.shortChannelId}")
|
||||
import d.commitments.{localParams, remoteParams}
|
||||
val channelAnn = Announcements.makeChannelAnnouncement(nodeParams.chainHash, localAnnSigs.shortChannelId, localParams.nodeId, remoteParams.nodeId, localParams.fundingPrivKey.publicKey, remoteParams.fundingPubKey, localAnnSigs.nodeSignature, remoteAnnSigs.nodeSignature, localAnnSigs.bitcoinSignature, remoteAnnSigs.bitcoinSignature)
|
||||
val nodeAnn = Announcements.makeNodeAnnouncement(nodeParams.privateKey, nodeParams.alias, nodeParams.color, nodeParams.publicAddresses)
|
||||
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, localAnnSigs.shortChannelId, nodeParams.expiryDeltaBlocks, nodeParams.htlcMinimumMsat, nodeParams.feeBaseMsat, nodeParams.feeProportionalMillionth)
|
||||
router ! channelAnn
|
||||
router ! nodeAnn
|
||||
router ! channelUpdate
|
||||
relayer ! channelUpdate
|
||||
// TODO: remove this later when we use testnet/mainnet
|
||||
// let's trigger the broadcast immediately so that we don't wait for 60 seconds to announce our newly created channel
|
||||
// we give 3 seconds for the router-watcher roundtrip
|
||||
context.system.scheduler.scheduleOnce(3 seconds, router, TickBroadcast)
|
||||
context.system.eventStream.publish(ShortChannelIdAssigned(self, d.channelId, localAnnSigs.shortChannelId))
|
||||
// we acknowledge our AnnouncementSignatures message
|
||||
stay using store(d.copy(shortChannelId = Some(localAnnSigs.shortChannelId))) // note: we don't clear our announcement sigs because we may need to re-send them
|
||||
case None =>
|
||||
log.info(s"received remote announcement signatures, delaying")
|
||||
(d.localAnnouncementSignatures, d.channelAnnouncement) match {
|
||||
case (None, _) =>
|
||||
// our watcher didn't notify yet that the tx has reached ANNOUNCEMENTS_MINCONF confirmations, let's delay remote's message
|
||||
// note: no need to persist their message, in case of disconnection they will resend it
|
||||
log.debug(s"received remote announcement signatures, delaying")
|
||||
context.system.scheduler.scheduleOnce(5 seconds, self, remoteAnnSigs)
|
||||
if (nodeParams.watcherType == BITCOINJ) {
|
||||
log.warning(s"HACK: since we cannot get the tx index with bitcoinj, we copy the value sent by remote")
|
||||
|
@ -722,36 +721,52 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
self ! WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, blockHeight, txIndex)
|
||||
}
|
||||
stay
|
||||
}
|
||||
|
||||
case Event(TickRefreshChannelUpdate, d: DATA_NORMAL) if d.shortChannelId.isDefined =>
|
||||
d.shortChannelId match {
|
||||
case Some(shortChannelId) => // periodic refresh is used as a keep alive
|
||||
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, shortChannelId, nodeParams.expiryDeltaBlocks, nodeParams.htlcMinimumMsat, nodeParams.feeBaseMsat, nodeParams.feeProportionalMillionth)
|
||||
case (Some(localAnnSigs), None) =>
|
||||
require(localAnnSigs.shortChannelId == remoteAnnSigs.shortChannelId, s"shortChannelId mismatch: local=${localAnnSigs.shortChannelId} remote=${remoteAnnSigs.shortChannelId}")
|
||||
log.info(s"announcing channelId=${d.channelId} on the network with shortId=${localAnnSigs.shortChannelId}")
|
||||
import d.commitments.{localParams, remoteParams}
|
||||
val channelAnn = Announcements.makeChannelAnnouncement(nodeParams.chainHash, localAnnSigs.shortChannelId, localParams.nodeId, remoteParams.nodeId, localParams.fundingPrivKey.publicKey, remoteParams.fundingPubKey, localAnnSigs.nodeSignature, remoteAnnSigs.nodeSignature, localAnnSigs.bitcoinSignature, remoteAnnSigs.bitcoinSignature)
|
||||
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, d.shortChannelId, d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, enable = true)
|
||||
router ! channelAnn
|
||||
router ! channelUpdate
|
||||
stay
|
||||
case None => stay // channel is not announced
|
||||
relayer ! channelUpdate
|
||||
stay using store(d.copy(channelAnnouncement = Some(channelAnn), channelUpdate = channelUpdate)) // note: we don't clear our announcement sigs because we may need to re-send them
|
||||
case (Some(localAnnSigs), Some(_)) =>
|
||||
// they send their announcement sigs, but we already have a valid channel annoucement
|
||||
// this can happen if our announcement_signatures was lost during a disconnection
|
||||
// specs says that we "MUST respond to the first announcement_signatures message after reconnection with its own announcement_signatures message"
|
||||
// current implementation always replies to announcement_signatures, not only the first time
|
||||
log.info(s"re-sending our announcement sigs")
|
||||
stay sending localAnnSigs
|
||||
}
|
||||
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_NORMAL) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
|
||||
case Event(TickRefreshChannelUpdate, d: DATA_NORMAL) =>
|
||||
// periodic refresh is used as a keep alive
|
||||
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, d.shortChannelId, d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, enable = true)
|
||||
if (d.channelAnnouncement.isDefined) {
|
||||
router ! channelUpdate
|
||||
}
|
||||
relayer ! channelUpdate
|
||||
stay using d.copy(channelUpdate = channelUpdate)
|
||||
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_NORMAL) if Some(tx.txid) == d.commitments.remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit.txid) => handleRemoteSpentNext(tx, d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx), d: DATA_NORMAL) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
|
||||
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_NORMAL) => handleRemoteSpentOther(tx, d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx), d: DATA_NORMAL) if Some(tx.txid) == d.commitments.remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit.txid) => handleRemoteSpentNext(tx, d)
|
||||
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx), d: DATA_NORMAL) => handleRemoteSpentOther(tx, d)
|
||||
|
||||
case Event(INPUT_DISCONNECTED, d: DATA_NORMAL) =>
|
||||
// we disable the channel
|
||||
log.info(s"disabling the channel (disconnected)")
|
||||
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, d.shortChannelId, d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, enable = false)
|
||||
if (d.channelAnnouncement.isDefined) {
|
||||
router ! channelUpdate
|
||||
}
|
||||
relayer ! channelUpdate
|
||||
d.commitments.localChanges.proposed.collect {
|
||||
case add: UpdateAddHtlc => relayer ! ForwardLocalFail(ChannelUnavailable(d.channelId), d.commitments.originChannels(add.id))
|
||||
case add: UpdateAddHtlc => relayer ! AddHtlcFailed(d.channelId, ChannelUnavailable(d.channelId), d.commitments.originChannels(add.id), Some(channelUpdate))
|
||||
}
|
||||
d.shortChannelId match {
|
||||
case Some(shortChannelId) =>
|
||||
// if channel has be announced, we disable it
|
||||
log.info(s"disabling the channel (disconnected)")
|
||||
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, shortChannelId, nodeParams.expiryDeltaBlocks, nodeParams.htlcMinimumMsat, nodeParams.feeBaseMsat, nodeParams.feeProportionalMillionth, enable = false)
|
||||
router ! channelUpdate
|
||||
case None => {}
|
||||
}
|
||||
goto(OFFLINE)
|
||||
goto(OFFLINE) using d.copy(channelUpdate = channelUpdate)
|
||||
|
||||
case Event(e: Error, d: DATA_NORMAL) => handleRemoteError(e, d)
|
||||
|
||||
|
@ -785,7 +800,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
relayer ! ForwardFulfill(fulfill, origin)
|
||||
stay using d.copy(commitments = commitments1)
|
||||
case Success(Left(_)) => stay
|
||||
case Failure(cause) => handleLocalError(cause, d)
|
||||
case Failure(cause) => handleLocalError(cause, d, Some(fulfill))
|
||||
}
|
||||
|
||||
case Event(c: CMD_FAIL_HTLC, d: DATA_SHUTDOWN) =>
|
||||
|
@ -810,7 +825,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
relayer ! ForwardFail(fail, origin)
|
||||
stay using d.copy(commitments = commitments1)
|
||||
case Success(Left(_)) => stay
|
||||
case Failure(cause) => handleLocalError(cause, d)
|
||||
case Failure(cause) => handleLocalError(cause, d, Some(fail))
|
||||
}
|
||||
|
||||
case Event(fail: UpdateFailMalformedHtlc, d: DATA_SHUTDOWN) =>
|
||||
|
@ -819,7 +834,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
relayer ! ForwardFailMalformed(fail, origin)
|
||||
stay using d.copy(commitments = commitments1)
|
||||
case Success(Left(_)) => stay
|
||||
case Failure(cause) => handleLocalError(cause, d)
|
||||
case Failure(cause) => handleLocalError(cause, d, Some(fail))
|
||||
}
|
||||
|
||||
case Event(c: CMD_UPDATE_FEE, d: DATA_SHUTDOWN) =>
|
||||
|
@ -833,7 +848,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
case Event(fee: UpdateFee, d: DATA_SHUTDOWN) =>
|
||||
Try(Commitments.receiveFee(d.commitments, fee, nodeParams.maxFeerateMismatch)) match {
|
||||
case Success(commitments1) => stay using d.copy(commitments = commitments1)
|
||||
case Failure(cause) => handleLocalError(cause, d)
|
||||
case Failure(cause) => handleLocalError(cause, d, Some(fee))
|
||||
}
|
||||
|
||||
case Event(CMD_SIGN, d: DATA_SHUTDOWN) =>
|
||||
|
@ -854,8 +869,8 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
stay using d.copy(commitments = d.commitments.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true))))
|
||||
}
|
||||
|
||||
case Event(msg: CommitSig, d@DATA_SHUTDOWN(_, localShutdown, remoteShutdown)) =>
|
||||
Try(Commitments.receiveCommit(d.commitments, msg)) map {
|
||||
case Event(commit: CommitSig, d@DATA_SHUTDOWN(_, localShutdown, remoteShutdown)) =>
|
||||
Try(Commitments.receiveCommit(d.commitments, commit)) map {
|
||||
case (commitments1, revocation) =>
|
||||
// we always reply with a revocation
|
||||
log.debug(s"received a new sig:\n${Commitments.specs2String(commitments1)}")
|
||||
|
@ -864,24 +879,24 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
} match {
|
||||
case Success((commitments1, revocation)) if commitments1.hasNoPendingHtlcs =>
|
||||
val closingSigned = Closing.makeFirstClosingTx(commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey)
|
||||
goto(NEGOTIATING) using store(DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, closingSigned)) sending revocation :: closingSigned :: Nil
|
||||
goto(NEGOTIATING) using store(DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, closingSigned :: Nil)) sending revocation :: closingSigned :: Nil
|
||||
case Success((commitments1, revocation)) =>
|
||||
if (Commitments.localHasChanges(commitments1)) {
|
||||
// if we have newly acknowledged changes let's sign them
|
||||
self ! CMD_SIGN
|
||||
}
|
||||
stay using store(d.copy(commitments = commitments1)) sending revocation
|
||||
case Failure(cause) => handleLocalError(cause, d)
|
||||
case Failure(cause) => handleLocalError(cause, d, Some(commit))
|
||||
}
|
||||
|
||||
case Event(msg: RevokeAndAck, d@DATA_SHUTDOWN(commitments, localShutdown, remoteShutdown)) =>
|
||||
case Event(revocation: RevokeAndAck, d@DATA_SHUTDOWN(commitments, localShutdown, remoteShutdown)) =>
|
||||
// we received a revocation because we sent a signature
|
||||
// => all our changes have been acked including the shutdown message
|
||||
Try(Commitments.receiveRevocation(commitments, msg)) match {
|
||||
Try(Commitments.receiveRevocation(commitments, revocation)) match {
|
||||
case Success(commitments1) if commitments1.hasNoPendingHtlcs =>
|
||||
log.debug(s"received a new rev, switching to NEGOTIATING spec:\n${Commitments.specs2String(commitments1)}")
|
||||
val closingSigned = Closing.makeFirstClosingTx(commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey)
|
||||
goto(NEGOTIATING) using store(DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, closingSigned)) sending closingSigned
|
||||
goto(NEGOTIATING) using store(DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, closingSigned :: Nil)) sending closingSigned
|
||||
case Success(commitments1) =>
|
||||
// BOLT 2: A sending node SHOULD fail to route any HTLC added after it sent shutdown.
|
||||
d.commitments.remoteChanges.signed.collect {
|
||||
|
@ -894,28 +909,28 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
}
|
||||
log.debug(s"received a new rev, spec:\n${Commitments.specs2String(commitments1)}")
|
||||
stay using store(d.copy(commitments = commitments1))
|
||||
case Failure(cause) => handleLocalError(cause, d)
|
||||
case Failure(cause) => handleLocalError(cause, d, Some(revocation))
|
||||
}
|
||||
|
||||
case Event(CurrentBlockCount(count), d: DATA_SHUTDOWN) if d.commitments.hasTimedoutOutgoingHtlcs(count) =>
|
||||
handleLocalError(HtlcTimedout(d.channelId), d)
|
||||
case Event(c@CurrentBlockCount(count), d: DATA_SHUTDOWN) if d.commitments.hasTimedoutOutgoingHtlcs(count) =>
|
||||
handleLocalError(HtlcTimedout(d.channelId), d, Some(c))
|
||||
|
||||
case Event(CurrentFeerates(feerates), d: DATA_SHUTDOWN) =>
|
||||
case Event(c@CurrentFeerates(feerates), d: DATA_SHUTDOWN) =>
|
||||
val networkFeeratePerKw = feerates.block_1
|
||||
d.commitments.localParams.isFunder match {
|
||||
case true if Helpers.shouldUpdateFee(d.commitments.localCommit.spec.feeratePerKw, networkFeeratePerKw, nodeParams.updateFeeMinDiffRatio) =>
|
||||
self ! CMD_UPDATE_FEE(networkFeeratePerKw, commit = true)
|
||||
stay
|
||||
case false if Helpers.isFeeDiffTooHigh(d.commitments.localCommit.spec.feeratePerKw, networkFeeratePerKw, nodeParams.maxFeerateMismatch) =>
|
||||
handleLocalError(FeerateTooDifferent(d.channelId, localFeeratePerKw = networkFeeratePerKw, remoteFeeratePerKw = d.commitments.localCommit.spec.feeratePerKw), d)
|
||||
handleLocalError(FeerateTooDifferent(d.channelId, localFeeratePerKw = networkFeeratePerKw, remoteFeeratePerKw = d.commitments.localCommit.spec.feeratePerKw), d, Some(c))
|
||||
case _ => stay
|
||||
}
|
||||
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_SHUTDOWN) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx), d: DATA_SHUTDOWN) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
|
||||
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_SHUTDOWN) if Some(tx.txid) == d.commitments.remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit.txid) => handleRemoteSpentNext(tx, d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx), d: DATA_SHUTDOWN) if Some(tx.txid) == d.commitments.remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit.txid) => handleRemoteSpentNext(tx, d)
|
||||
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_SHUTDOWN) => handleRemoteSpentOther(tx, d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx), d: DATA_SHUTDOWN) => handleRemoteSpentOther(tx, d)
|
||||
|
||||
case Event(CMD_CLOSE(_), d: DATA_SHUTDOWN) => handleCommandError(ClosingAlreadyInProgress(d.channelId))
|
||||
|
||||
|
@ -924,32 +939,34 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
})
|
||||
|
||||
when(NEGOTIATING)(handleExceptions {
|
||||
case Event(ClosingSigned(_, remoteClosingFee, remoteSig), d: DATA_NEGOTIATING) =>
|
||||
case Event(c@ClosingSigned(_, remoteClosingFee, remoteSig), d: DATA_NEGOTIATING) =>
|
||||
log.info(s"received closingFeeSatoshis=$remoteClosingFee")
|
||||
Closing.checkClosingSignature(d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, Satoshi(remoteClosingFee), remoteSig) match {
|
||||
case Success(signedClosingTx) if remoteClosingFee == d.localClosingSigned.feeSatoshis =>
|
||||
handleMutualClose(signedClosingTx, d)
|
||||
case Success(signedClosingTx) if remoteClosingFee == d.localClosingSigned.last.feeSatoshis =>
|
||||
doPublish(signedClosingTx)
|
||||
handleMutualClose(signedClosingTx, Left(d))
|
||||
case Success(signedClosingTx) =>
|
||||
val nextClosingFee = Closing.nextClosingFee(Satoshi(d.localClosingSigned.feeSatoshis), Satoshi(remoteClosingFee))
|
||||
val nextClosingFee = Closing.nextClosingFee(Satoshi(d.localClosingSigned.last.feeSatoshis), Satoshi(remoteClosingFee))
|
||||
val (_, closingSigned) = Closing.makeClosingTx(d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nextClosingFee)
|
||||
log.info(s"proposing closingFeeSatoshis=${closingSigned.feeSatoshis}")
|
||||
if (nextClosingFee == Satoshi(remoteClosingFee)) {
|
||||
handleMutualClose(signedClosingTx, store(d)) sending closingSigned
|
||||
doPublish(signedClosingTx)
|
||||
handleMutualClose(signedClosingTx, Left(store(d))) sending closingSigned
|
||||
} else {
|
||||
stay using store(d.copy(localClosingSigned = closingSigned)) sending closingSigned
|
||||
stay using store(d.copy(localClosingSigned = d.localClosingSigned :+ closingSigned)) sending closingSigned
|
||||
}
|
||||
case Failure(cause) =>
|
||||
log.error(cause, "cannot verify their close signature")
|
||||
throw InvalidCloseSignature(d.channelId)
|
||||
case Failure(cause) => handleLocalError(cause, d, Some(c))
|
||||
}
|
||||
|
||||
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 =>
|
||||
// happens when we agreed on a closeSig, but we don't know it yet: we receive the watcher notification before their ClosingSigned (which will match ours)
|
||||
stay
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx), d: DATA_NEGOTIATING) if d.localClosingSigned.exists(closingSigned => tx.txIn.head.witness.stack.contains(closingSigned.signature)) =>
|
||||
// they can publish a closing tx with any sig we sent them, even if we are not done negotiating
|
||||
handleMutualClose(tx, Left(d))
|
||||
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_NEGOTIATING) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx), d: DATA_NEGOTIATING) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
|
||||
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_NEGOTIATING) if Some(tx.txid) == d.commitments.remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit.txid) => handleRemoteSpentNext(tx, d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx), d: DATA_NEGOTIATING) if Some(tx.txid) == d.commitments.remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit.txid) => handleRemoteSpentNext(tx, d)
|
||||
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_NEGOTIATING) => handleRemoteSpentOther(tx, d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx), d: DATA_NEGOTIATING) => handleRemoteSpentOther(tx, d)
|
||||
|
||||
case Event(CMD_CLOSE(_), d: DATA_NEGOTIATING) => handleCommandError(ClosingAlreadyInProgress(d.channelId))
|
||||
|
||||
|
@ -984,10 +1001,10 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
case Failure(cause) => handleCommandError(cause)
|
||||
}
|
||||
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_CLOSING) =>
|
||||
if (Some(tx.txid) == d.mutualClosePublished.map(_.txid)) {
|
||||
// we just published a mutual close tx, we are notified but it's alright
|
||||
stay
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx), d: DATA_CLOSING) =>
|
||||
if (d.localClosingSigned.exists(closingSigned => tx.txIn.head.witness.stack.contains(closingSigned.signature))) {
|
||||
// at any time they can publish a closing tx with any sig we sent them
|
||||
handleMutualClose(tx, Right(d))
|
||||
} else if (Some(tx.txid) == d.localCommitPublished.map(_.commitTx.txid)) {
|
||||
// this is because WatchSpent watches never expire and we are notified multiple times
|
||||
stay
|
||||
|
@ -1008,7 +1025,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
handleRemoteSpentOther(tx, d)
|
||||
}
|
||||
|
||||
case Event(WatchEventSpent(BITCOIN_OUTPUT_SPENT, tx: Transaction), d: DATA_CLOSING) =>
|
||||
case Event(WatchEventSpent(BITCOIN_OUTPUT_SPENT, tx), d: DATA_CLOSING) =>
|
||||
// one of the outputs of the local/remote/revoked commit was spent
|
||||
// we just put a watch to be notified when it is confirmed
|
||||
blockchain ! WatchConfirmed(self, tx, nodeParams.minDepthBlocks, BITCOIN_TX_CONFIRMED(tx))
|
||||
|
@ -1049,7 +1066,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
case add if ripemd160(add.paymentHash) == extracted =>
|
||||
val origin = d.commitments.originChannels(add.id)
|
||||
log.warning(s"found a match between paymentHash160=$extracted and origin=$origin: htlc timed out")
|
||||
relayer ! ForwardLocalFail(HtlcTimedout(d.channelId), origin)
|
||||
relayer ! AddHtlcFailed(d.channelId, HtlcTimedout(d.channelId), origin, None)
|
||||
}
|
||||
// TODO: should we handle local htlcs here as well? currently timed out htlcs that we sent will never have an answer
|
||||
// TODO: we do not handle the case where htlcs transactions end up being unconfirmed this can happen if an htlc-success tx is published right before a htlc timed out
|
||||
|
@ -1063,7 +1080,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
val nextRemoteCommitPublished1 = d.nextRemoteCommitPublished.map(Closing.updateRemoteCommitPublished(_, tx))
|
||||
val revokedCommitPublished1 = d.revokedCommitPublished.map(Closing.updateRevokedCommitPublished(_, tx))
|
||||
// then let's see if any of the possible close scenarii can be considered done
|
||||
val mutualCloseDone = d.mutualClosePublished.map(_.txid == tx.txid).getOrElse(false) // this case is trivial, in a mutual close scenario we only need to make sure that the closing tx is confirmed
|
||||
val mutualCloseDone = d.mutualClosePublished.exists(_.txid == tx.txid) // this case is trivial, in a mutual close scenario we only need to make sure that one of the closing txes is confirmed
|
||||
val localCommitDone = localCommitPublished1.map(Closing.isLocalCommitDone(_)).getOrElse(false)
|
||||
val remoteCommitDone = remoteCommitPublished1.map(Closing.isRemoteCommitDone(_)).getOrElse(false)
|
||||
val nextRemoteCommitDone = nextRemoteCommitPublished1.map(Closing.isRemoteCommitDone(_)).getOrElse(false)
|
||||
|
@ -1077,9 +1094,16 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
stay using d1
|
||||
}
|
||||
|
||||
case Event(_: ChannelReestablish, d: DATA_CLOSING) =>
|
||||
// they haven't detected that we were closing and are trying to reestablish a connection
|
||||
// we give them one of the published txes as a hint
|
||||
val exc = FundingTxSpent(d.channelId, d.spendingTxes.head) // spendingTx != Nil that's a requirement of DATA_CLOSING)
|
||||
val error = Error(d.channelId, exc.getMessage.getBytes)
|
||||
stay sending error
|
||||
|
||||
case Event(CMD_CLOSE(_), d: DATA_CLOSING) => handleCommandError(ClosingAlreadyInProgress(d.channelId))
|
||||
|
||||
case Event(e: Error, d: DATA_CLOSING) => stay // nothing to do, there is already a spending tx published
|
||||
case Event(e: Error, d: DATA_CLOSING) => handleRemoteError(e, d)
|
||||
|
||||
case Event(INPUT_DISCONNECTED | INPUT_RECONNECTED(_), _) => stay // we don't really care at this point
|
||||
})
|
||||
|
@ -1114,19 +1138,19 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
)
|
||||
goto(SYNCING) sending channelReestablish
|
||||
|
||||
case Event(CMD_CLOSE(_), d: HasCommitments) => handleLocalError(ForcedLocalCommit(d.channelId, "can't do a mutual close while disconnected"), d) replying "ok"
|
||||
case Event(c@CMD_CLOSE(_), d: HasCommitments) => handleLocalError(ForcedLocalCommit(d.channelId, "can't do a mutual close while disconnected"), d, Some(c)) replying "ok"
|
||||
|
||||
case Event(CurrentBlockCount(count), d: HasCommitments) if d.commitments.hasTimedoutOutgoingHtlcs(count) =>
|
||||
case Event(c@CurrentBlockCount(count), d: HasCommitments) if d.commitments.hasTimedoutOutgoingHtlcs(count) =>
|
||||
// note: this can only happen if state is NORMAL or SHUTDOWN
|
||||
// -> in NEGOTIATING there are no more htlcs
|
||||
// -> in CLOSING we either have mutual closed (so no more htlcs), or already have unilaterally closed (so no action required), and we can't be in OFFLINE state anyway
|
||||
handleLocalError(HtlcTimedout(d.channelId), d)
|
||||
handleLocalError(HtlcTimedout(d.channelId), d, Some(c))
|
||||
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: HasCommitments) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx), d: HasCommitments) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
|
||||
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: HasCommitments) if Some(tx.txid) == d.commitments.remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit.txid) => handleRemoteSpentNext(tx, d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx), d: HasCommitments) if Some(tx.txid) == d.commitments.remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit.txid) => handleRemoteSpentNext(tx, d)
|
||||
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: HasCommitments) => handleRemoteSpentOther(tx, d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx), d: HasCommitments) => handleRemoteSpentOther(tx, d)
|
||||
|
||||
})
|
||||
|
||||
|
@ -1160,26 +1184,27 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
forwarder ! localShutdown
|
||||
}
|
||||
|
||||
// even if we were just disconnected/reconnected, we need to put back the watch because the event may have been
|
||||
// fired while we were in OFFLINE (if not, the operation is idempotent anyway)
|
||||
// NB: in BITCOINJ mode we currently can't get the tx index in block (which is used to calculate the short id)
|
||||
// instead, we rely on a hack by trusting the index the counterparty sends us
|
||||
if (d.commitments.announceChannel && d.localAnnouncementSignatures.isEmpty && nodeParams.watcherType != BITCOINJ) {
|
||||
blockchain ! WatchConfirmed(self, d.commitments.commitInput.outPoint.txid, d.commitments.commitInput.txOut.publicKeyScript, ANNOUNCEMENTS_MINCONF, BITCOIN_FUNDING_DEEPLYBURIED)
|
||||
}
|
||||
// rfc: a node SHOULD retransmit the announcement_signatures message if it has not received an announcement_signatures message
|
||||
if (d.localAnnouncementSignatures.isDefined && d.shortChannelId.isEmpty) {
|
||||
forwarder ! d.localAnnouncementSignatures.get
|
||||
}
|
||||
|
||||
d.shortChannelId.map {
|
||||
case shortChannelId =>
|
||||
// we re-enable the channel
|
||||
log.info(s"enabling the channel (reconnected)")
|
||||
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, shortChannelId, nodeParams.expiryDeltaBlocks, nodeParams.htlcMinimumMsat, nodeParams.feeBaseMsat, nodeParams.feeProportionalMillionth, enable = true)
|
||||
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, d.shortChannelId, nodeParams.expiryDeltaBlocks, d.commitments.remoteParams.htlcMinimumMsat, nodeParams.feeBaseMsat, nodeParams.feeProportionalMillionth, enable = true)
|
||||
(d.localAnnouncementSignatures, d.channelAnnouncement) match {
|
||||
case (None, None) if nodeParams.watcherType != BITCOINJ =>
|
||||
// even if we were just disconnected/reconnected, we need to put back the watch because the event may have been
|
||||
// fired while we were in OFFLINE (if not, the operation is idempotent anyway)
|
||||
blockchain ! WatchConfirmed(self, d.commitments.commitInput.outPoint.txid, d.commitments.commitInput.txOut.publicKeyScript, ANNOUNCEMENTS_MINCONF, BITCOIN_FUNDING_DEEPLYBURIED)
|
||||
case (None, None) if nodeParams.watcherType == BITCOINJ =>
|
||||
// NB: in BITCOINJ mode we currently can't get the tx index in block (which is used to calculate the short id)
|
||||
// instead, we rely on a hack by trusting the index the counterparty sends us)
|
||||
case (Some(localAnnSigs), None) =>
|
||||
// BOLT 7: a node SHOULD retransmit the announcement_signatures message if it has not received an announcement_signatures message
|
||||
forwarder ! localAnnSigs
|
||||
case (_, Some(channelAnn)) =>
|
||||
// we might have been down for a long time (more than 2 weeks) and other nodes in the network might have forgotten the channel
|
||||
log.info(s"re-announcing channelId=${d.channelId} on the network with shortId=${channelAnn.shortChannelId}")
|
||||
router ! channelAnn
|
||||
router ! channelUpdate
|
||||
}
|
||||
goto(NORMAL) using d.copy(commitments = commitments1)
|
||||
relayer ! channelUpdate
|
||||
|
||||
goto(NORMAL) using d.copy(commitments = commitments1, channelUpdate = channelUpdate)
|
||||
|
||||
case Event(channelReestablish: ChannelReestablish, d: DATA_SHUTDOWN) =>
|
||||
val commitments1 = handleSync(channelReestablish, d)
|
||||
|
@ -1190,16 +1215,15 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
forwarder ! d.localClosingSigned
|
||||
goto(NEGOTIATING)
|
||||
|
||||
case Event(CMD_CLOSE(_), d: HasCommitments) => handleLocalError(ForcedLocalCommit(d.channelId, "can't do a mutual close while syncing"), d)
|
||||
case Event(c@CMD_CLOSE(_), d: HasCommitments) => handleLocalError(ForcedLocalCommit(d.channelId, "can't do a mutual close while syncing"), d, Some(c))
|
||||
|
||||
case Event(CurrentBlockCount(count), d: HasCommitments) if d.commitments.hasTimedoutOutgoingHtlcs(count) =>
|
||||
handleLocalError(HtlcTimedout(d.channelId), d)
|
||||
case Event(c@CurrentBlockCount(count), d: HasCommitments) if d.commitments.hasTimedoutOutgoingHtlcs(count) => handleLocalError(HtlcTimedout(d.channelId), d, Some(c))
|
||||
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: HasCommitments) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx), d: HasCommitments) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
|
||||
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: HasCommitments) if Some(tx.txid) == d.commitments.remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit.txid) => handleRemoteSpentNext(tx, d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx), d: HasCommitments) if Some(tx.txid) == d.commitments.remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit.txid) => handleRemoteSpentNext(tx, d)
|
||||
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: HasCommitments) => handleRemoteSpentOther(tx, d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx), d: HasCommitments) => handleRemoteSpentOther(tx, d)
|
||||
|
||||
case Event(e: Error, d: HasCommitments) => handleRemoteError(e, d)
|
||||
})
|
||||
|
@ -1218,7 +1242,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
|
||||
whenUnhandled {
|
||||
|
||||
case Event(INPUT_PUBLISH_LOCALCOMMIT, d: HasCommitments) => handleLocalError(ForcedLocalCommit(d.channelId, "manual unilateral close"), d)
|
||||
case Event(c@INPUT_PUBLISH_LOCALCOMMIT, d: HasCommitments) => handleLocalError(ForcedLocalCommit(d.channelId, "manual unilateral close"), d, Some(c))
|
||||
|
||||
case Event(INPUT_DISCONNECTED, _) => goto(OFFLINE)
|
||||
|
||||
|
@ -1240,7 +1264,10 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
case Event(c: CMD_ADD_HTLC, d: HasCommitments) =>
|
||||
log.info(s"rejecting htlc request in state=$stateName")
|
||||
val error = ChannelUnavailable(d.channelId)
|
||||
handleCommandAddError(error, origin(c))
|
||||
d match {
|
||||
case normal: DATA_NORMAL => handleCommandError(AddHtlcFailed(d.channelId, error, origin(c), Some(normal.channelUpdate))) // can happen if we are in OFFLINE or SYNCING state (channelUpdate will have enable=false)
|
||||
case _ => handleCommandError(AddHtlcFailed(d.channelId, error, origin(c), None))
|
||||
}
|
||||
|
||||
// we only care about this event in NORMAL and SHUTDOWN state, and we never unregister to the event stream
|
||||
case Event(CurrentBlockCount(_), _) => stay
|
||||
|
@ -1282,15 +1309,6 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
stay using newData replying "ok"
|
||||
}
|
||||
|
||||
def handleCommandAddError(cause: Throwable, origin: Origin) = {
|
||||
relayer ! ForwardLocalFail(cause, origin)
|
||||
cause match {
|
||||
case _: ChannelException => log.error(s"$cause")
|
||||
case _ => log.error(cause, "")
|
||||
}
|
||||
stay
|
||||
}
|
||||
|
||||
def handleCommandError(cause: Throwable) = {
|
||||
cause match {
|
||||
case _: ChannelException => log.error(s"$cause")
|
||||
|
@ -1299,28 +1317,39 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
stay replying Status.Failure(cause)
|
||||
}
|
||||
|
||||
def handleLocalError(cause: Throwable, d: HasCommitments) = {
|
||||
log.error(cause, "")
|
||||
def handleLocalError(cause: Throwable, d: HasCommitments, msg: Option[Any]) = {
|
||||
log.error(cause, s"error while processing msg=${msg.getOrElse("n/a")} in state=$stateData ")
|
||||
val error = Error(d.channelId, cause.getMessage.getBytes)
|
||||
spendLocalCurrent(d) sending error
|
||||
}
|
||||
|
||||
def handleRemoteErrorNoCommitments(e: Error) = {
|
||||
// when there is no commitment yet, we just go to CLOSED state in case an error occurs
|
||||
log.error(s"peer sent $e, closing connection") // see bolt #2: A node MUST fail the connection if it receives an err message
|
||||
goto(CLOSED)
|
||||
def handleRemoteError(e: Error, d: Data) = {
|
||||
// see BOLT 1: only print out data verbatim if is composed of printable ASCII characters
|
||||
log.error(s"peer sent error: ascii='${if (isAsciiPrintable(e.data)) new String(e.data, StandardCharsets.US_ASCII) else "n/a"}' bin=${e.data}")
|
||||
d match {
|
||||
case _: DATA_CLOSING => stay // nothing to do, there is already a spending tx published
|
||||
//case negotiating: DATA_NEGOTIATING => stay TODO: (nitpick) would be nice to publish a closing tx instead if we have already received one of their sigs
|
||||
case hasCommitments: HasCommitments => spendLocalCurrent(hasCommitments)
|
||||
case _ => goto(CLOSED) // when there is no commitment yet, we just go to CLOSED state in case an error occurs
|
||||
}
|
||||
}
|
||||
|
||||
def handleRemoteError(e: Error, d: HasCommitments) = {
|
||||
log.error(s"peer sent $e, closing connection") // see bolt #2: A node MUST fail the connection if it receives an err message
|
||||
spendLocalCurrent(d)
|
||||
}
|
||||
def handleMutualClose(closingTx: Transaction, d: Either[DATA_NEGOTIATING, DATA_CLOSING]) = {
|
||||
log.info(s"a closing tx has been published: closingTxId=${closingTx.txid}")
|
||||
|
||||
def handleMutualClose(closingTx: Transaction, d: DATA_NEGOTIATING) = {
|
||||
log.info(s"closingTxId=${closingTx.txid}")
|
||||
val mutualClosePublished = Some(closingTx)
|
||||
doPublish(closingTx)
|
||||
val nextData = DATA_CLOSING(d.commitments, mutualClosePublished)
|
||||
val closingSigned = d match {
|
||||
case Left(negotiating) => negotiating.localClosingSigned
|
||||
case Right(closing) => closing.localClosingSigned
|
||||
}
|
||||
val index = closingSigned.indexWhere(closingSigned => closingTx.txIn.head.witness.stack.contains(closingSigned.signature))
|
||||
if (index != closingSigned.size - 1) {
|
||||
log.warning(s"closing tx was published before end of negotiation: closingTxId=${closingTx.txid} index=$index signatures=${closingSigned.size}")
|
||||
}
|
||||
|
||||
val nextData = d match {
|
||||
case Left(negotiating) => DATA_CLOSING(negotiating.commitments, negotiating.localClosingSigned, mutualClosePublished = closingTx :: Nil)
|
||||
case Right(closing) => closing.copy(mutualClosePublished = closing.mutualClosePublished :+ closingTx)
|
||||
}
|
||||
goto(CLOSING) using store(nextData)
|
||||
}
|
||||
|
||||
|
@ -1337,7 +1366,8 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
|
||||
val nextData = d match {
|
||||
case closing: DATA_CLOSING => closing.copy(localCommitPublished = Some(localCommitPublished))
|
||||
case _ => DATA_CLOSING(d.commitments, localCommitPublished = Some(localCommitPublished))
|
||||
case negotiating: DATA_NEGOTIATING => DATA_CLOSING(d.commitments, negotiating.localClosingSigned, localCommitPublished = Some(localCommitPublished))
|
||||
case _ => DATA_CLOSING(d.commitments, localClosingSigned = Nil, localCommitPublished = Some(localCommitPublished))
|
||||
}
|
||||
|
||||
goto(CLOSING) using store(nextData)
|
||||
|
@ -1369,7 +1399,8 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
|
||||
val nextData = d match {
|
||||
case closing: DATA_CLOSING => closing.copy(remoteCommitPublished = Some(remoteCommitPublished))
|
||||
case _ => DATA_CLOSING(d.commitments, remoteCommitPublished = Some(remoteCommitPublished))
|
||||
case negotiating: DATA_NEGOTIATING => DATA_CLOSING(d.commitments, negotiating.localClosingSigned, remoteCommitPublished = Some(remoteCommitPublished))
|
||||
case _ => DATA_CLOSING(d.commitments, localClosingSigned = Nil, remoteCommitPublished = Some(remoteCommitPublished))
|
||||
}
|
||||
|
||||
goto(CLOSING) using store(nextData)
|
||||
|
@ -1386,7 +1417,8 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
|
||||
val nextData = d match {
|
||||
case closing: DATA_CLOSING => closing.copy(nextRemoteCommitPublished = Some(remoteCommitPublished))
|
||||
case _ => DATA_CLOSING(d.commitments, nextRemoteCommitPublished = Some(remoteCommitPublished))
|
||||
case negotiating: DATA_NEGOTIATING => DATA_CLOSING(d.commitments, negotiating.localClosingSigned, nextRemoteCommitPublished = Some(remoteCommitPublished))
|
||||
case _ => DATA_CLOSING(d.commitments, localClosingSigned = Nil, nextRemoteCommitPublished = Some(remoteCommitPublished))
|
||||
}
|
||||
|
||||
goto(CLOSING) using store(nextData)
|
||||
|
@ -1412,13 +1444,15 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
Helpers.Closing.claimRevokedRemoteCommitTxOutputs(d.commitments, tx) match {
|
||||
case Some(revokedCommitPublished) =>
|
||||
log.warning(s"txid=${tx.txid} was a revoked commitment, publishing the penalty tx")
|
||||
val error = Error(d.channelId, "Funding tx has been spent".getBytes)
|
||||
val exc = FundingTxSpent(d.channelId, tx)
|
||||
val error = Error(d.channelId, exc.getMessage.getBytes)
|
||||
|
||||
doPublish(revokedCommitPublished)
|
||||
|
||||
val nextData = d match {
|
||||
case closing: DATA_CLOSING => closing.copy(revokedCommitPublished = closing.revokedCommitPublished :+ revokedCommitPublished)
|
||||
case _ => DATA_CLOSING(d.commitments, revokedCommitPublished = revokedCommitPublished :: Nil)
|
||||
case negotiating: DATA_NEGOTIATING => DATA_CLOSING(d.commitments, negotiating.localClosingSigned, revokedCommitPublished = revokedCommitPublished :: Nil)
|
||||
case _ => DATA_CLOSING(d.commitments, localClosingSigned = Nil, revokedCommitPublished = revokedCommitPublished :: Nil)
|
||||
}
|
||||
goto(CLOSING) using store(nextData) sending error
|
||||
case None =>
|
||||
|
@ -1446,10 +1480,11 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
revokedCommitPublished.htlcTimeoutTxs.foreach(tx => blockchain ! WatchSpent(self, revokedCommitPublished.commitTx, tx.txIn.head.outPoint.index.toInt, BITCOIN_OUTPUT_SPENT))
|
||||
}
|
||||
|
||||
def handleInformationLeak(d: HasCommitments) = {
|
||||
def handleInformationLeak(tx: Transaction, d: HasCommitments) = {
|
||||
// this is never supposed to happen !!
|
||||
log.error(s"our funding tx ${d.commitments.commitInput.outPoint.txid} was spent !!")
|
||||
val error = Error(d.channelId, "Funding tx has been spent".getBytes)
|
||||
log.error(s"our funding tx ${d.commitments.commitInput.outPoint.txid} was spent by txid=${tx.txid} !!")
|
||||
val exc = FundingTxSpent(d.channelId, tx)
|
||||
val error = Error(d.channelId, exc.getMessage.getBytes)
|
||||
|
||||
// let's try to spend our current local tx
|
||||
val commitTx = d.commitments.localCommit.publishableTxs.commitTx.tx
|
||||
|
@ -1546,7 +1581,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
s(event)
|
||||
} catch {
|
||||
case t: Throwable => event.stateData match {
|
||||
case d: HasCommitments => handleLocalError(t, d)
|
||||
case d: HasCommitments => handleLocalError(t, d, None)
|
||||
case d: Data =>
|
||||
log.error(t, "")
|
||||
val error = Error(Helpers.getChannelId(d), t.getMessage.getBytes)
|
||||
|
|
|
@ -1,48 +1,57 @@
|
|||
package fr.acinq.eclair.channel
|
||||
|
||||
import fr.acinq.bitcoin.BinaryData
|
||||
import fr.acinq.bitcoin.{BinaryData, Transaction}
|
||||
import fr.acinq.eclair.UInt64
|
||||
import fr.acinq.eclair.payment.Origin
|
||||
import fr.acinq.eclair.wire.ChannelUpdate
|
||||
|
||||
/**
|
||||
* Created by PM on 11/04/2017.
|
||||
*/
|
||||
|
||||
class ChannelException(channelId: BinaryData, message: String) extends RuntimeException(message) { def getChannelId() = channelId }
|
||||
class ChannelException(val channelId: BinaryData, message: String) extends RuntimeException(message)
|
||||
// @formatter:off
|
||||
case class DebugTriggeredException (channelId: BinaryData) extends ChannelException(channelId, "debug-mode triggered failure")
|
||||
case class InvalidChainHash (channelId: BinaryData, local: BinaryData, remote: BinaryData) extends ChannelException(channelId, s"invalid chain_hash (local=$local remote=$remote)")
|
||||
case class InvalidFundingAmount (channelId: BinaryData, fundingSatoshis: Long, min: Long, max: Long) extends ChannelException(channelId, s"invalid funding_satoshis=$fundingSatoshis (min=$min max=$max)")
|
||||
case class InvalidPushAmount (channelId: BinaryData, pushMsat: Long, max: Long) extends ChannelException(channelId, s"invalid push_msat=$pushMsat (max=$max)")
|
||||
case class InvalidMaxAcceptedHtlcs (channelId: BinaryData, maxAcceptedHtlcs: Int, max: Int) extends ChannelException(channelId, s"invalid max_accepted_htlcs=$maxAcceptedHtlcs (max=$max)")
|
||||
case class InvalidDustLimit (channelId: BinaryData, dustLimitSatoshis: Long, min: Long) extends ChannelException(channelId, s"invalid dust_limit_satoshis=$dustLimitSatoshis (min=$min)")
|
||||
case class ChannelReserveTooHigh (channelId: BinaryData, channelReserveSatoshis: Long, reserveToFundingRatio: Double, maxReserveToFundingRatio: Double) extends ChannelException(channelId, s"channelReserveSatoshis too high: reserve=$channelReserveSatoshis fundingRatio=$reserveToFundingRatio maxFundingRatio=$maxReserveToFundingRatio")
|
||||
case class ClosingInProgress (channelId: BinaryData) extends ChannelException(channelId, "cannot send new htlcs, closing in progress")
|
||||
case class ClosingAlreadyInProgress (channelId: BinaryData) extends ChannelException(channelId, "closing already in progress")
|
||||
case class CannotCloseWithUnsignedOutgoingHtlcs(channelId: BinaryData) extends ChannelException(channelId, "cannot close when there are unsigned outgoing htlcs")
|
||||
case class ChannelUnavailable (channelId: BinaryData) extends ChannelException(channelId, "channel is unavailable (offline or closing)")
|
||||
case class InvalidFinalScript (channelId: BinaryData) extends ChannelException(channelId, "invalid final script")
|
||||
case class HtlcTimedout (channelId: BinaryData) extends ChannelException(channelId, s"one or more htlcs timed out")
|
||||
case class FeerateTooDifferent (channelId: BinaryData, localFeeratePerKw: Long, remoteFeeratePerKw: Long) extends ChannelException(channelId, s"local/remote feerates are too different: remoteFeeratePerKw=$remoteFeeratePerKw localFeeratePerKw=$localFeeratePerKw")
|
||||
case class InvalidCloseSignature (channelId: BinaryData) extends ChannelException(channelId, "cannot verify their close signature")
|
||||
case class InvalidCommitmentSignature (channelId: BinaryData) extends ChannelException(channelId, "invalid commitment signature")
|
||||
case class ForcedLocalCommit (channelId: BinaryData, reason: String) extends ChannelException(channelId, s"forced local commit: reason")
|
||||
case class UnexpectedHtlcId (channelId: BinaryData, expected: Long, actual: Long) extends ChannelException(channelId, s"unexpected htlc id: expected=$expected actual=$actual")
|
||||
case class InvalidPaymentHash (channelId: BinaryData) extends ChannelException(channelId, "invalid payment hash")
|
||||
case class ExpiryTooSmall (channelId: BinaryData, minimum: Long, actual: Long, blockCount: Long) extends ChannelException(channelId, s"expiry too small: required=$minimum actual=$actual blockCount=$blockCount")
|
||||
case class ExpiryCannotBeInThePast (channelId: BinaryData, expiry: Long, blockCount: Long) extends ChannelException(channelId, s"expiry can't be in the past: expiry=$expiry blockCount=$blockCount")
|
||||
case class HtlcValueTooSmall (channelId: BinaryData, minimum: Long, actual: Long) extends ChannelException(channelId, s"htlc value too small: minimum=$minimum actual=$actual")
|
||||
case class HtlcValueTooHighInFlight (channelId: BinaryData, maximum: UInt64, actual: UInt64) extends ChannelException(channelId, s"in-flight htlcs hold too much value: maximum=$maximum actual=$actual")
|
||||
case class TooManyAcceptedHtlcs (channelId: BinaryData, maximum: Long) extends ChannelException(channelId, s"too many accepted htlcs: maximum=$maximum")
|
||||
case class InsufficientFunds (channelId: BinaryData, amountMsat: Long, missingSatoshis: Long, reserveSatoshis: Long, feesSatoshis: Long) extends ChannelException(channelId, s"insufficient funds: missingSatoshis=$missingSatoshis reserveSatoshis=$reserveSatoshis fees=$feesSatoshis")
|
||||
case class InvalidHtlcPreimage (channelId: BinaryData, id: Long) extends ChannelException(channelId, s"invalid htlc preimage for htlc id=$id")
|
||||
case class UnknownHtlcId (channelId: BinaryData, id: Long) extends ChannelException(channelId, s"unknown htlc id=$id")
|
||||
case class FundeeCannotSendUpdateFee (channelId: BinaryData) extends ChannelException(channelId, s"only the funder should send update_fee messages")
|
||||
case class CannotAffordFees (channelId: BinaryData, missingSatoshis: Long, reserveSatoshis: Long, feesSatoshis: Long) extends ChannelException(channelId, s"can't pay the fee: missingSatoshis=$missingSatoshis reserveSatoshis=$reserveSatoshis feesSatoshis=$feesSatoshis")
|
||||
case class CannotSignWithoutChanges (channelId: BinaryData) extends ChannelException(channelId, "cannot sign when there are no changes")
|
||||
case class CannotSignBeforeRevocation (channelId: BinaryData) extends ChannelException(channelId, "cannot sign until next revocation hash is received")
|
||||
case class UnexpectedRevocation (channelId: BinaryData) extends ChannelException(channelId, "received unexpected RevokeAndAck message")
|
||||
case class InvalidRevocation (channelId: BinaryData) extends ChannelException(channelId, "invalid revocation")
|
||||
case class CommitmentSyncError (channelId: BinaryData) extends ChannelException(channelId, "commitment sync error")
|
||||
case class RevocationSyncError (channelId: BinaryData) extends ChannelException(channelId, "revocation sync error")
|
||||
case class InvalidFailureCode (channelId: BinaryData) extends ChannelException(channelId, "UpdateFailMalformedHtlc message doesn't have BADONION bit set")
|
||||
case class DebugTriggeredException (override val channelId: BinaryData) extends ChannelException(channelId, "debug-mode triggered failure")
|
||||
case class InvalidChainHash (override val channelId: BinaryData, local: BinaryData, remote: BinaryData) extends ChannelException(channelId, s"invalid chain_hash (local=$local remote=$remote)")
|
||||
case class InvalidFundingAmount (override val channelId: BinaryData, fundingSatoshis: Long, min: Long, max: Long) extends ChannelException(channelId, s"invalid funding_satoshis=$fundingSatoshis (min=$min max=$max)")
|
||||
case class InvalidPushAmount (override val channelId: BinaryData, pushMsat: Long, max: Long) extends ChannelException(channelId, s"invalid push_msat=$pushMsat (max=$max)")
|
||||
case class InvalidMaxAcceptedHtlcs (override val channelId: BinaryData, maxAcceptedHtlcs: Int, max: Int) extends ChannelException(channelId, s"invalid max_accepted_htlcs=$maxAcceptedHtlcs (max=$max)")
|
||||
case class InvalidDustLimit (override val channelId: BinaryData, dustLimitSatoshis: Long, min: Long) extends ChannelException(channelId, s"invalid dust_limit_satoshis=$dustLimitSatoshis (min=$min)")
|
||||
case class ChannelReserveTooHigh (override val channelId: BinaryData, channelReserveSatoshis: Long, reserveToFundingRatio: Double, maxReserveToFundingRatio: Double) extends ChannelException(channelId, s"channelReserveSatoshis too high: reserve=$channelReserveSatoshis fundingRatio=$reserveToFundingRatio maxFundingRatio=$maxReserveToFundingRatio")
|
||||
case class ChannelFundingError (override val channelId: BinaryData) extends ChannelException(channelId, "channel funding error")
|
||||
case class NoMoreHtlcsClosingInProgress (override val channelId: BinaryData) extends ChannelException(channelId, "cannot send new htlcs, closing in progress")
|
||||
case class ClosingAlreadyInProgress (override val channelId: BinaryData) extends ChannelException(channelId, "closing already in progress")
|
||||
case class CannotCloseWithUnsignedOutgoingHtlcs(override val channelId: BinaryData) extends ChannelException(channelId, "cannot close when there are unsigned outgoing htlcs")
|
||||
case class ChannelUnavailable (override val channelId: BinaryData) extends ChannelException(channelId, "channel is unavailable (offline or closing)")
|
||||
case class InvalidFinalScript (override val channelId: BinaryData) extends ChannelException(channelId, "invalid final script")
|
||||
case class FundingTxTimedout (override val channelId: BinaryData) extends ChannelException(channelId, "funding tx timed out")
|
||||
case class FundingTxSpent (override val channelId: BinaryData, spendingTx: Transaction) extends ChannelException(channelId, s"funding tx has been spent by txid=${spendingTx.txid}")
|
||||
case class HtlcTimedout (override val channelId: BinaryData) extends ChannelException(channelId, "one or more htlcs timed out")
|
||||
case class FeerateTooDifferent (override val channelId: BinaryData, localFeeratePerKw: Long, remoteFeeratePerKw: Long) extends ChannelException(channelId, s"local/remote feerates are too different: remoteFeeratePerKw=$remoteFeeratePerKw localFeeratePerKw=$localFeeratePerKw")
|
||||
case class InvalidCommitmentSignature (override val channelId: BinaryData, tx: Transaction) extends ChannelException(channelId, s"invalid commitment signature: tx=${Transaction.write(tx)}")
|
||||
case class InvalidHtlcSignature (override val channelId: BinaryData, tx: Transaction) extends ChannelException(channelId, s"invalid htlc signature: tx=${Transaction.write(tx)}")
|
||||
case class InvalidCloseSignature (override val channelId: BinaryData, tx: Transaction) extends ChannelException(channelId, s"invalid close signature: tx=${Transaction.write(tx)}")
|
||||
case class InvalidCloseFee (override val channelId: BinaryData, feeSatoshi: Long) extends ChannelException(channelId, s"invalid close fee: fee_satoshis=$feeSatoshi")
|
||||
case class HtlcSigCountMismatch (override val channelId: BinaryData, expected: Int, actual: Int) extends ChannelException(channelId, s"htlc sig count mismatch: expected=$expected actual: $actual")
|
||||
case class ForcedLocalCommit (override val channelId: BinaryData, reason: String) extends ChannelException(channelId, s"forced local commit: reason")
|
||||
case class UnexpectedHtlcId (override val channelId: BinaryData, expected: Long, actual: Long) extends ChannelException(channelId, s"unexpected htlc id: expected=$expected actual=$actual")
|
||||
case class InvalidPaymentHash (override val channelId: BinaryData) extends ChannelException(channelId, "invalid payment hash")
|
||||
case class ExpiryTooSmall (override val channelId: BinaryData, minimum: Long, actual: Long, blockCount: Long) extends ChannelException(channelId, s"expiry too small: required=$minimum actual=$actual blockCount=$blockCount")
|
||||
case class ExpiryCannotBeInThePast (override val channelId: BinaryData, expiry: Long, blockCount: Long) extends ChannelException(channelId, s"expiry can't be in the past: expiry=$expiry blockCount=$blockCount")
|
||||
case class HtlcValueTooSmall (override val channelId: BinaryData, minimum: Long, actual: Long) extends ChannelException(channelId, s"htlc value too small: minimum=$minimum actual=$actual")
|
||||
case class HtlcValueTooHighInFlight (override val channelId: BinaryData, maximum: UInt64, actual: UInt64) extends ChannelException(channelId, s"in-flight htlcs hold too much value: maximum=$maximum actual=$actual")
|
||||
case class TooManyAcceptedHtlcs (override val channelId: BinaryData, maximum: Long) extends ChannelException(channelId, s"too many accepted htlcs: maximum=$maximum")
|
||||
case class InsufficientFunds (override val channelId: BinaryData, amountMsat: Long, missingSatoshis: Long, reserveSatoshis: Long, feesSatoshis: Long) extends ChannelException(channelId, s"insufficient funds: missingSatoshis=$missingSatoshis reserveSatoshis=$reserveSatoshis fees=$feesSatoshis")
|
||||
case class InvalidHtlcPreimage (override val channelId: BinaryData, id: Long) extends ChannelException(channelId, s"invalid htlc preimage for htlc id=$id")
|
||||
case class UnknownHtlcId (override val channelId: BinaryData, id: Long) extends ChannelException(channelId, s"unknown htlc id=$id")
|
||||
case class FundeeCannotSendUpdateFee (override val channelId: BinaryData) extends ChannelException(channelId, s"only the funder should send update_fee messages")
|
||||
case class CannotAffordFees (override val channelId: BinaryData, missingSatoshis: Long, reserveSatoshis: Long, feesSatoshis: Long) extends ChannelException(channelId, s"can't pay the fee: missingSatoshis=$missingSatoshis reserveSatoshis=$reserveSatoshis feesSatoshis=$feesSatoshis")
|
||||
case class CannotSignWithoutChanges (override val channelId: BinaryData) extends ChannelException(channelId, "cannot sign when there are no changes")
|
||||
case class CannotSignBeforeRevocation (override val channelId: BinaryData) extends ChannelException(channelId, "cannot sign until next revocation hash is received")
|
||||
case class UnexpectedRevocation (override val channelId: BinaryData) extends ChannelException(channelId, "received unexpected RevokeAndAck message")
|
||||
case class InvalidRevocation (override val channelId: BinaryData) extends ChannelException(channelId, "invalid revocation")
|
||||
case class CommitmentSyncError (override val channelId: BinaryData) extends ChannelException(channelId, "commitment sync error")
|
||||
case class RevocationSyncError (override val channelId: BinaryData) extends ChannelException(channelId, "revocation sync error")
|
||||
case class InvalidFailureCode (override val channelId: BinaryData) extends ChannelException(channelId, "UpdateFailMalformedHtlc message doesn't have BADONION bit set")
|
||||
case class AddHtlcFailed (override val channelId: BinaryData, t: Throwable, origin: Origin, channelUpdate: Option[ChannelUpdate]) extends ChannelException(channelId, s"cannot add htlc with origin=$origin reason=${t.getMessage}")
|
||||
// @formatter:on
|
|
@ -7,7 +7,7 @@ import fr.acinq.eclair.UInt64
|
|||
import fr.acinq.eclair.crypto.Sphinx
|
||||
import fr.acinq.eclair.transactions.CommitmentSpec
|
||||
import fr.acinq.eclair.transactions.Transactions.CommitTx
|
||||
import fr.acinq.eclair.wire.{AcceptChannel, AnnouncementSignatures, ClosingSigned, FailureMessage, FundingCreated, FundingLocked, FundingSigned, Init, OpenChannel, Shutdown, UpdateAddHtlc}
|
||||
import fr.acinq.eclair.wire.{AcceptChannel, AnnouncementSignatures, ChannelAnnouncement, ChannelUpdate, ClosingSigned, FailureMessage, FundingCreated, FundingLocked, FundingSigned, Init, OpenChannel, Shutdown, UpdateAddHtlc}
|
||||
|
||||
|
||||
/**
|
||||
|
@ -133,23 +133,27 @@ final case class DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId: BinaryData,
|
|||
final case class DATA_WAIT_FOR_FUNDING_CREATED(temporaryChannelId: BinaryData, localParams: LocalParams, remoteParams: RemoteParams, fundingSatoshis: Long, pushMsat: Long, initialFeeratePerKw: Long, remoteFirstPerCommitmentPoint: Point, channelFlags: Byte, lastSent: AcceptChannel) extends Data
|
||||
final case class DATA_WAIT_FOR_FUNDING_SIGNED(channelId: BinaryData, localParams: LocalParams, remoteParams: RemoteParams, fundingTx: Transaction, localSpec: CommitmentSpec, localCommitTx: CommitTx, remoteCommit: RemoteCommit, channelFlags: Byte, lastSent: FundingCreated) extends Data
|
||||
final case class DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments: Commitments, deferred: Option[FundingLocked], lastSent: Either[FundingCreated, FundingSigned]) extends Data with HasCommitments
|
||||
final case class DATA_WAIT_FOR_FUNDING_LOCKED(commitments: Commitments, lastSent: FundingLocked) extends Data with HasCommitments
|
||||
final case class DATA_WAIT_FOR_FUNDING_LOCKED(commitments: Commitments, shortChannelId: Long, lastSent: FundingLocked) extends Data with HasCommitments
|
||||
final case class DATA_NORMAL(commitments: Commitments,
|
||||
shortChannelId: Option[Long],
|
||||
shortChannelId: Long,
|
||||
channelAnnouncement: Option[ChannelAnnouncement],
|
||||
channelUpdate: ChannelUpdate,
|
||||
localAnnouncementSignatures: Option[AnnouncementSignatures],
|
||||
localShutdown: Option[Shutdown],
|
||||
remoteShutdown: Option[Shutdown]) extends Data with HasCommitments
|
||||
final case class DATA_SHUTDOWN(commitments: Commitments,
|
||||
localShutdown: Shutdown, remoteShutdown: Shutdown) extends Data with HasCommitments
|
||||
final case class DATA_NEGOTIATING(commitments: Commitments,
|
||||
localShutdown: Shutdown, remoteShutdown: Shutdown, localClosingSigned: ClosingSigned) extends Data with HasCommitments
|
||||
localShutdown: Shutdown, remoteShutdown: Shutdown, localClosingSigned: List[ClosingSigned]) extends Data with HasCommitments
|
||||
final case class DATA_CLOSING(commitments: Commitments,
|
||||
mutualClosePublished: Option[Transaction] = None,
|
||||
localClosingSigned: List[ClosingSigned],
|
||||
mutualClosePublished: List[Transaction] = Nil,
|
||||
localCommitPublished: Option[LocalCommitPublished] = None,
|
||||
remoteCommitPublished: Option[RemoteCommitPublished] = None,
|
||||
nextRemoteCommitPublished: Option[RemoteCommitPublished] = None,
|
||||
revokedCommitPublished: List[RevokedCommitPublished] = Nil) extends Data with HasCommitments {
|
||||
require(mutualClosePublished.isDefined || localCommitPublished.isDefined || remoteCommitPublished.isDefined || nextRemoteCommitPublished.isDefined || revokedCommitPublished.size > 0, "there should be at least one tx published in this state")
|
||||
val spendingTxes = mutualClosePublished ::: localCommitPublished.map(_.commitTx).toList ::: remoteCommitPublished.map(_.commitTx).toList ::: nextRemoteCommitPublished.map(_.commitTx).toList ::: revokedCommitPublished.map(_.commitTx)
|
||||
require(spendingTxes.size > 0, "there must be at least one tx published in this state")
|
||||
}
|
||||
|
||||
final case class LocalParams(nodeId: PublicKey,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package fr.acinq.eclair.channel
|
||||
|
||||
import akka.event.LoggingAdapter
|
||||
import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, sha256}
|
||||
import fr.acinq.bitcoin.{BinaryData, Crypto, Satoshi, Transaction}
|
||||
import fr.acinq.eclair.crypto.{Generators, ShaChain, Sphinx}
|
||||
|
@ -8,7 +9,6 @@ import fr.acinq.eclair.transactions.Transactions._
|
|||
import fr.acinq.eclair.transactions._
|
||||
import fr.acinq.eclair.wire._
|
||||
import fr.acinq.eclair.{Globals, UInt64}
|
||||
import grizzled.slf4j.Logging
|
||||
|
||||
// @formatter:off
|
||||
case class LocalChanges(proposed: List[UpdateMessage], signed: List[UpdateMessage], acked: List[UpdateMessage]) {
|
||||
|
@ -54,7 +54,7 @@ case class Commitments(localParams: LocalParams, remoteParams: RemoteParams,
|
|||
def announceChannel: Boolean = (channelFlags & 0x01) != 0
|
||||
}
|
||||
|
||||
object Commitments extends Logging {
|
||||
object Commitments {
|
||||
/**
|
||||
* add a change to our proposed change list
|
||||
*
|
||||
|
@ -355,7 +355,7 @@ object Commitments extends Logging {
|
|||
}
|
||||
}
|
||||
|
||||
def receiveCommit(commitments: Commitments, commit: CommitSig): (Commitments, RevokeAndAck) = {
|
||||
def receiveCommit(commitments: Commitments, commit: CommitSig)(implicit log: LoggingAdapter): (Commitments, RevokeAndAck) = {
|
||||
import commitments._
|
||||
// they sent us a signature for *their* view of *our* next commit tx
|
||||
// so in terms of rev.hashes and indexes we have:
|
||||
|
@ -383,22 +383,28 @@ 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 InvalidCommitmentSignature(commitments.channelId)
|
||||
throw InvalidCommitmentSignature(commitments.channelId, signedCommitTx.tx)
|
||||
}
|
||||
|
||||
val sortedHtlcTxs: Seq[TransactionWithInputInfo] = (htlcTimeoutTxs ++ htlcSuccessTxs).sortBy(_.input.outPoint.index)
|
||||
require(commit.htlcSignatures.size == sortedHtlcTxs.size, s"htlc sig count mismatch (received=${commit.htlcSignatures.size}, expected=${sortedHtlcTxs.size})")
|
||||
if (commit.htlcSignatures.size != sortedHtlcTxs.size) {
|
||||
throw new HtlcSigCountMismatch(commitments.channelId, sortedHtlcTxs.size, commit.htlcSignatures.size)
|
||||
}
|
||||
val localHtlcKey = Generators.derivePrivKey(localParams.htlcKey, localPerCommitmentPoint)
|
||||
val htlcSigs = sortedHtlcTxs.map(Transactions.sign(_, localHtlcKey))
|
||||
val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint)
|
||||
// combine the sigs to make signed txes
|
||||
val htlcTxsAndSigs = (sortedHtlcTxs, htlcSigs, commit.htlcSignatures).zipped.toList.collect {
|
||||
case (htlcTx: HtlcTimeoutTx, localSig, remoteSig) =>
|
||||
require(Transactions.checkSpendable(Transactions.addSigs(htlcTx, localSig, remoteSig)).isSuccess, "bad sig")
|
||||
if (Transactions.checkSpendable(Transactions.addSigs(htlcTx, localSig, remoteSig)).isFailure) {
|
||||
throw new InvalidHtlcSignature(commitments.channelId, htlcTx.tx)
|
||||
}
|
||||
HtlcTxAndSigs(htlcTx, localSig, remoteSig)
|
||||
case (htlcTx: HtlcSuccessTx, localSig, remoteSig) =>
|
||||
// we can't check that htlc-success tx are spendable because we need the payment preimage; thus we only check the remote sig
|
||||
require(Transactions.checkSig(htlcTx, remoteSig, remoteHtlcPubkey), "bad sig")
|
||||
if (Transactions.checkSig(htlcTx, remoteSig, remoteHtlcPubkey) == false) {
|
||||
throw new InvalidHtlcSignature(commitments.channelId, htlcTx.tx)
|
||||
}
|
||||
HtlcTxAndSigs(htlcTx, localSig, remoteSig)
|
||||
}
|
||||
|
||||
|
@ -424,7 +430,7 @@ object Commitments extends Logging {
|
|||
val originChannels1 = commitments.originChannels -- completedOutgoingHtlcs
|
||||
val commitments1 = commitments.copy(localCommit = localCommit1, localChanges = ourChanges1, remoteChanges = theirChanges1, originChannels = originChannels1)
|
||||
|
||||
logger.debug(s"current commit: index=${localCommit1.index} htlc_in=${localCommit1.spec.htlcs.filter(_.direction == IN).size} htlc_out=${localCommit1.spec.htlcs.filter(_.direction == OUT).size} txid=${localCommit1.publishableTxs.commitTx.tx.txid} tx=${Transaction.write(localCommit1.publishableTxs.commitTx.tx)}")
|
||||
log.debug(s"current commit: index=${localCommit1.index} htlc_in=${localCommit1.spec.htlcs.filter(_.direction == IN).size} htlc_out=${localCommit1.spec.htlcs.filter(_.direction == OUT).size} txid=${localCommit1.publishableTxs.commitTx.tx.txid} tx=${Transaction.write(localCommit1.publishableTxs.commitTx.tx)}")
|
||||
|
||||
(commitments1, revocation)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package fr.acinq.eclair.channel
|
||||
|
||||
import akka.event.LoggingAdapter
|
||||
import fr.acinq.bitcoin.Crypto.{Point, PublicKey, Scalar, sha256}
|
||||
import fr.acinq.bitcoin.Script._
|
||||
import fr.acinq.bitcoin.{OutPoint, _}
|
||||
|
@ -11,7 +12,6 @@ import fr.acinq.eclair.transactions.Transactions._
|
|||
import fr.acinq.eclair.transactions._
|
||||
import fr.acinq.eclair.wire._
|
||||
import fr.acinq.eclair.{Globals, NodeParams}
|
||||
import grizzled.slf4j.Logging
|
||||
|
||||
import scala.concurrent.Await
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
@ -157,7 +157,7 @@ object Helpers {
|
|||
|
||||
}
|
||||
|
||||
object Closing extends Logging {
|
||||
object Closing {
|
||||
|
||||
def isValidFinalScriptPubkey(scriptPubKey: BinaryData): Boolean = {
|
||||
Try(Script.parse(scriptPubKey)) match {
|
||||
|
@ -169,23 +169,24 @@ object Helpers {
|
|||
}
|
||||
}
|
||||
|
||||
def makeFirstClosingTx(commitments: Commitments, localScriptPubkey: BinaryData, remoteScriptPubkey: BinaryData): ClosingSigned = {
|
||||
logger.debug(s"making first closing tx with commitments:\n${Commitments.specs2String(commitments)}")
|
||||
def makeFirstClosingTx(commitments: Commitments, localScriptPubkey: BinaryData, remoteScriptPubkey: BinaryData)(implicit log: LoggingAdapter): ClosingSigned = {
|
||||
log.debug(s"making first closing tx with commitments:\n${Commitments.specs2String(commitments)}")
|
||||
import commitments._
|
||||
val closingFee = {
|
||||
// this is just to estimate the weight, it depends on size of the pubkey scripts
|
||||
val dummyClosingTx = Transactions.makeClosingTx(commitInput, localScriptPubkey, remoteScriptPubkey, localParams.isFunder, Satoshi(0), Satoshi(0), localCommit.spec)
|
||||
val closingWeight = Transaction.weight(Transactions.addSigs(dummyClosingTx, localParams.fundingPrivKey.publicKey, remoteParams.fundingPubKey, "aa" * 71, "bb" * 71).tx)
|
||||
// no need to use a very high fee here
|
||||
val feeratePerKw = Globals.feeratesPerKw.get.blocks_6
|
||||
logger.info(s"using feeratePerKw=$feeratePerKw for closing tx")
|
||||
// no need to use a very high fee here, so we target 6 blocks; also, we "MUST set fee_satoshis less than or equal to the base fee of the final commitment transaction"
|
||||
val feeratePerKw = Math.min(Globals.feeratesPerKw.get.blocks_6, commitments.localCommit.spec.feeratePerKw)
|
||||
log.info(s"using feeratePerKw=$feeratePerKw for initial closing tx")
|
||||
Transactions.weight2fee(feeratePerKw, closingWeight)
|
||||
}
|
||||
val (_, closingSigned) = makeClosingTx(commitments, localScriptPubkey, remoteScriptPubkey, closingFee)
|
||||
log.info(s"proposing closingFeeSatoshis=${closingSigned.feeSatoshis}")
|
||||
closingSigned
|
||||
}
|
||||
|
||||
def makeClosingTx(commitments: Commitments, localScriptPubkey: BinaryData, remoteScriptPubkey: BinaryData, closingFee: Satoshi): (ClosingTx, ClosingSigned) = {
|
||||
def makeClosingTx(commitments: Commitments, localScriptPubkey: BinaryData, remoteScriptPubkey: BinaryData, closingFee: Satoshi)(implicit log: LoggingAdapter): (ClosingTx, ClosingSigned) = {
|
||||
import commitments._
|
||||
require(isValidFinalScriptPubkey(localScriptPubkey), "invalid localScriptPubkey")
|
||||
require(isValidFinalScriptPubkey(remoteScriptPubkey), "invalid remoteScriptPubkey")
|
||||
|
@ -194,26 +195,31 @@ object Helpers {
|
|||
val closingTx = Transactions.makeClosingTx(commitInput, localScriptPubkey, remoteScriptPubkey, localParams.isFunder, dustLimitSatoshis, closingFee, localCommit.spec)
|
||||
val localClosingSig = Transactions.sign(closingTx, commitments.localParams.fundingPrivKey)
|
||||
val closingSigned = ClosingSigned(channelId, closingFee.amount, localClosingSig)
|
||||
logger.debug(s"closingTx=${Transaction.write(closingTx.tx)}")
|
||||
log.debug(s"closingTx=${Transaction.write(closingTx.tx)}")
|
||||
(closingTx, closingSigned)
|
||||
}
|
||||
|
||||
def checkClosingSignature(commitments: Commitments, localScriptPubkey: BinaryData, remoteScriptPubkey: BinaryData, remoteClosingFee: Satoshi, remoteClosingSig: BinaryData): Try[Transaction] = {
|
||||
def checkClosingSignature(commitments: Commitments, localScriptPubkey: BinaryData, remoteScriptPubkey: BinaryData, remoteClosingFee: Satoshi, remoteClosingSig: BinaryData)(implicit log: LoggingAdapter): Try[Transaction] = {
|
||||
import commitments._
|
||||
val lastCommitFeeSatoshi = commitments.commitInput.txOut.amount.amount - commitments.localCommit.publishableTxs.commitTx.tx.txOut.map(_.amount.amount).sum
|
||||
if (remoteClosingFee.amount > lastCommitFeeSatoshi) {
|
||||
log.error(s"remote proposed a commit fee higher than the last commitment fee: remoteClosingFeeSatoshi=${remoteClosingFee.amount} lastCommitFeeSatoshi=$lastCommitFeeSatoshi")
|
||||
throw new InvalidCloseFee(commitments.channelId, remoteClosingFee.amount)
|
||||
}
|
||||
val (closingTx, closingSigned) = makeClosingTx(commitments, localScriptPubkey, remoteScriptPubkey, remoteClosingFee)
|
||||
val signedClosingTx = Transactions.addSigs(closingTx, localParams.fundingPrivKey.publicKey, remoteParams.fundingPubKey, closingSigned.signature, remoteClosingSig)
|
||||
Transactions.checkSpendable(signedClosingTx).map(x => signedClosingTx.tx)
|
||||
Transactions.checkSpendable(signedClosingTx).map(x => signedClosingTx.tx).recover { case _ => throw InvalidCloseSignature(commitments.channelId, signedClosingTx.tx) }
|
||||
}
|
||||
|
||||
def nextClosingFee(localClosingFee: Satoshi, remoteClosingFee: Satoshi): Satoshi = ((localClosingFee + remoteClosingFee) / 4) * 2
|
||||
|
||||
def generateTx(desc: String)(attempt: Try[TransactionWithInputInfo]): Option[TransactionWithInputInfo] = {
|
||||
def generateTx(desc: String)(attempt: Try[TransactionWithInputInfo])(implicit log: LoggingAdapter): Option[TransactionWithInputInfo] = {
|
||||
attempt match {
|
||||
case Success(txinfo) =>
|
||||
logger.warn(s"tx generation success: desc=$desc txid=${txinfo.tx.txid} amount=${txinfo.tx.txOut.map(_.amount.amount).sum} tx=${Transaction.write(txinfo.tx)}")
|
||||
log.warning(s"tx generation success: desc=$desc txid=${txinfo.tx.txid} amount=${txinfo.tx.txOut.map(_.amount.amount).sum} tx=${Transaction.write(txinfo.tx)}")
|
||||
Some(txinfo)
|
||||
case Failure(t) =>
|
||||
logger.warn(s"tx generation failure: desc=$desc reason: ${t.getMessage}")
|
||||
log.warning(s"tx generation failure: desc=$desc reason: ${t.getMessage}")
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -226,7 +232,7 @@ object Helpers {
|
|||
* @param commitments our commitment data, which include payment preimages
|
||||
* @return a list of transactions (one per HTLC that we can claim)
|
||||
*/
|
||||
def claimCurrentLocalCommitTxOutputs(commitments: Commitments, tx: Transaction): LocalCommitPublished = {
|
||||
def claimCurrentLocalCommitTxOutputs(commitments: Commitments, tx: Transaction)(implicit log: LoggingAdapter): LocalCommitPublished = {
|
||||
import commitments._
|
||||
require(localCommit.publishableTxs.commitTx.tx.txid == tx.txid, "txid mismatch, provided tx is not the current local commit tx")
|
||||
|
||||
|
@ -294,7 +300,7 @@ object Helpers {
|
|||
* @param commitments our commitment data, which include payment preimages
|
||||
* @return a list of transactions (one per HTLC that we can claim)
|
||||
*/
|
||||
def claimRemoteCommitTxOutputs(commitments: Commitments, remoteCommit: RemoteCommit, tx: Transaction): RemoteCommitPublished = {
|
||||
def claimRemoteCommitTxOutputs(commitments: Commitments, remoteCommit: RemoteCommit, tx: Transaction)(implicit log: LoggingAdapter): RemoteCommitPublished = {
|
||||
import commitments.{commitInput, localParams, remoteParams}
|
||||
require(remoteCommit.txid == tx.txid, "txid mismatch, provided tx is not the current remote commit tx")
|
||||
val (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = Commitments.makeRemoteTxs(remoteCommit.index, localParams, remoteParams, commitInput, remoteCommit.remotePerCommitmentPoint, remoteCommit.spec)
|
||||
|
@ -364,14 +370,14 @@ object Helpers {
|
|||
*
|
||||
* @return a [[RevokedCommitPublished]] object containing penalty transactions if the tx is a revoked commitment
|
||||
*/
|
||||
def claimRevokedRemoteCommitTxOutputs(commitments: Commitments, tx: Transaction): Option[RevokedCommitPublished] = {
|
||||
def claimRevokedRemoteCommitTxOutputs(commitments: Commitments, tx: Transaction)(implicit log: LoggingAdapter): Option[RevokedCommitPublished] = {
|
||||
import commitments._
|
||||
require(tx.txIn.size == 1, "commitment tx should have 1 input")
|
||||
val obscuredTxNumber = Transactions.decodeTxNumber(tx.txIn(0).sequence, tx.lockTime)
|
||||
// this tx has been published by remote, so we need to invert local/remote params
|
||||
val txnumber = Transactions.obscuredCommitTxNumber(obscuredTxNumber, !localParams.isFunder, remoteParams.paymentBasepoint, localParams.paymentBasepoint)
|
||||
require(txnumber <= 0xffffffffffffL, "txnumber must be lesser than 48 bits long")
|
||||
logger.warn(s"counterparty has published revoked commit txnumber=$txnumber")
|
||||
log.warning(s"counterparty has published revoked commit txnumber=$txnumber")
|
||||
// now we know what commit number this tx is referring to, we can derive the commitment point from the shachain
|
||||
remotePerCommitmentSecrets.getHash(0xFFFFFFFFFFFFL - txnumber)
|
||||
.map(d => Scalar(d))
|
||||
|
|
|
@ -140,7 +140,7 @@ class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, address_opt: Option[
|
|||
}
|
||||
stay
|
||||
|
||||
case Event(Pong(data), ConnectedData(transport, _, _)) =>
|
||||
case Event(Pong(data), ConnectedData(_, _, _)) =>
|
||||
// TODO: compute latency for remote peer ?
|
||||
log.debug(s"received pong with ${data.length} bytes")
|
||||
stay
|
||||
|
@ -151,30 +151,14 @@ class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, address_opt: Option[
|
|||
transport ! PoisonPill
|
||||
stay
|
||||
|
||||
case Event(msg: Error, ConnectedData(_, _, channels)) =>
|
||||
case Event(msg: Error, ConnectedData(transport, _, channels)) =>
|
||||
// error messages are a bit special because they can contain either temporaryChannelId or channelId (see BOLT 1)
|
||||
channels.get(TemporaryChannelId(msg.channelId)).orElse(channels.get(FinalChannelId(msg.channelId))) match {
|
||||
channels.get(FinalChannelId(msg.channelId)).orElse(channels.get(TemporaryChannelId(msg.channelId))) match {
|
||||
case Some(channel) => channel forward msg
|
||||
case None => log.warning(s"couldn't resolve channel for $msg")
|
||||
case None => transport ! Error(msg.channelId, UNKNOWN_CHANNEL_MESSAGE)
|
||||
}
|
||||
stay
|
||||
|
||||
case Event(msg: HasTemporaryChannelId, ConnectedData(_, _, channels)) if channels.contains(TemporaryChannelId(msg.temporaryChannelId)) =>
|
||||
val channel = channels(TemporaryChannelId(msg.temporaryChannelId))
|
||||
channel forward msg
|
||||
stay
|
||||
|
||||
case Event(msg: HasChannelId, ConnectedData(_, _, channels)) if channels.contains(FinalChannelId(msg.channelId)) =>
|
||||
val channel = channels(FinalChannelId(msg.channelId))
|
||||
channel forward msg
|
||||
stay
|
||||
|
||||
case Event(ChannelIdAssigned(channel, temporaryChannelId, channelId), d@ConnectedData(_, _, channels)) if channels.contains(TemporaryChannelId(temporaryChannelId)) =>
|
||||
log.info(s"channel id switch: previousId=$temporaryChannelId nextId=$channelId")
|
||||
// NB: we keep the temporary channel id because the switch is not always acknowledged at this point (see https://github.com/lightningnetwork/lightning-rfc/pull/151)
|
||||
// we won't clean it up, but we won't remember the temporary id on channel termination
|
||||
stay using d.copy(channels = channels + (FinalChannelId(channelId) -> channel))
|
||||
|
||||
case Event(c: NewChannel, d@ConnectedData(transport, remoteInit, channels)) =>
|
||||
log.info(s"requesting a new channel to $remoteNodeId with fundingSatoshis=${c.fundingSatoshis} and pushMsat=${c.pushMsat}")
|
||||
val (channel, localParams) = createChannel(nodeParams, transport, funder = true, c.fundingSatoshis.toLong)
|
||||
|
@ -183,13 +167,39 @@ class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, address_opt: Option[
|
|||
channel ! INPUT_INIT_FUNDER(temporaryChannelId, c.fundingSatoshis.amount, c.pushMsat.amount, networkFeeratePerKw, localParams, transport, remoteInit, c.channelFlags.getOrElse(nodeParams.channelFlags))
|
||||
stay using d.copy(channels = channels + (TemporaryChannelId(temporaryChannelId) -> channel))
|
||||
|
||||
case Event(msg: OpenChannel, d@ConnectedData(transport, remoteInit, channels)) if !channels.contains(TemporaryChannelId(msg.temporaryChannelId)) =>
|
||||
log.info(s"accepting a new channel to $remoteNodeId")
|
||||
val (channel, localParams) = createChannel(nodeParams, transport, funder = false, fundingSatoshis = msg.fundingSatoshis)
|
||||
val temporaryChannelId = msg.temporaryChannelId
|
||||
channel ! INPUT_INIT_FUNDEE(temporaryChannelId, localParams, transport, remoteInit)
|
||||
channel ! msg
|
||||
stay using d.copy(channels = channels + (TemporaryChannelId(temporaryChannelId) -> channel))
|
||||
case Event(msg: OpenChannel, d@ConnectedData(transport, remoteInit, channels)) =>
|
||||
channels.get(TemporaryChannelId(msg.temporaryChannelId)) match {
|
||||
case None =>
|
||||
log.info(s"accepting a new channel to $remoteNodeId")
|
||||
val (channel, localParams) = createChannel(nodeParams, transport, funder = false, fundingSatoshis = msg.fundingSatoshis)
|
||||
val temporaryChannelId = msg.temporaryChannelId
|
||||
channel ! INPUT_INIT_FUNDEE(temporaryChannelId, localParams, transport, remoteInit)
|
||||
channel ! msg
|
||||
stay using d.copy(channels = channels + (TemporaryChannelId(temporaryChannelId) -> channel))
|
||||
case Some(_) =>
|
||||
log.warning(s"ignoring open_channel with duplicate temporaryChannelId=${msg.temporaryChannelId}")
|
||||
stay
|
||||
}
|
||||
|
||||
case Event(msg: HasChannelId, ConnectedData(transport, _, channels)) =>
|
||||
channels.get(FinalChannelId(msg.channelId)) match {
|
||||
case Some(channel) => channel forward msg
|
||||
case None => transport ! Error(msg.channelId, UNKNOWN_CHANNEL_MESSAGE)
|
||||
}
|
||||
stay
|
||||
|
||||
case Event(msg: HasTemporaryChannelId, ConnectedData(transport, _, channels)) =>
|
||||
channels.get(TemporaryChannelId(msg.temporaryChannelId)) match {
|
||||
case Some(channel) => channel forward msg
|
||||
case None => transport ! Error(msg.temporaryChannelId, UNKNOWN_CHANNEL_MESSAGE)
|
||||
}
|
||||
stay
|
||||
|
||||
case Event(ChannelIdAssigned(channel, temporaryChannelId, channelId), d@ConnectedData(_, _, channels)) if channels.contains(TemporaryChannelId(temporaryChannelId)) =>
|
||||
log.info(s"channel id switch: previousId=$temporaryChannelId nextId=$channelId")
|
||||
// NB: we keep the temporary channel id because the switch is not always acknowledged at this point (see https://github.com/lightningnetwork/lightning-rfc/pull/151)
|
||||
// we won't clean it up, but we won't remember the temporary id on channel termination
|
||||
stay using d.copy(channels = channels + (FinalChannelId(channelId) -> channel))
|
||||
|
||||
case Event(Rebroadcast(announcements, origins), ConnectedData(transport, _, _)) =>
|
||||
// we filter out announcements that we received from this node
|
||||
|
@ -259,6 +269,8 @@ object Peer {
|
|||
|
||||
val CHANNELID_ZERO = BinaryData("00" * 32)
|
||||
|
||||
val UNKNOWN_CHANNEL_MESSAGE = "unknown channel".getBytes()
|
||||
|
||||
def props(nodeParams: NodeParams, remoteNodeId: PublicKey, address_opt: Option[InetSocketAddress], watcher: ActorRef, router: ActorRef, relayer: ActorRef, wallet: EclairWallet, storedChannels: Set[HasCommitments]) = Props(new Peer(nodeParams, remoteNodeId, address_opt, watcher, router, relayer, wallet: EclairWallet, storedChannels))
|
||||
|
||||
def generateKey(nodeParams: NodeParams, keyPath: Seq[Long]): PrivateKey = DeterministicWallet.derivePrivateKey(nodeParams.extendedPrivateKey, keyPath).privateKey
|
||||
|
|
|
@ -72,4 +72,11 @@ package object eclair {
|
|||
* way to tell what the script is.
|
||||
*/
|
||||
def isSegwitAddress(address: String) : Boolean = address.startsWith("2") || address.startsWith("3")
|
||||
|
||||
/**
|
||||
* Tests whether the binary data is composed solely of printable ASCII characters (see BOLT 1)
|
||||
*
|
||||
* @param data to check
|
||||
*/
|
||||
def isAsciiPrintable(data: BinaryData): Boolean = data.data.forall(ch => ch >= 32 && ch < 127)
|
||||
}
|
|
@ -56,12 +56,7 @@ class PaymentLifecycle(sourceNodeId: PublicKey, router: ActorRef, register: Acto
|
|||
val firstHop = hops.head
|
||||
val finalExpiry = Globals.blockCount.get().toInt + c.minFinalCltvExpiry.toInt
|
||||
val (cmd, sharedSecrets) = buildCommand(c.amountMsat, finalExpiry, c.paymentHash, hops)
|
||||
// TODO: HACK!!!! see Router.scala (we actually store the first node id in the sig)
|
||||
if (firstHop.lastUpdate.signature.size == 32) {
|
||||
register ! Register.Forward(firstHop.lastUpdate.signature, cmd)
|
||||
} else {
|
||||
register ! Register.ForwardShortId(firstHop.lastUpdate.shortChannelId, cmd)
|
||||
}
|
||||
register ! Register.ForwardShortId(firstHop.lastUpdate.shortChannelId, cmd)
|
||||
goto(WAITING_FOR_PAYMENT_COMPLETE) using WaitingForComplete(s, c, cmd, failures, sharedSecrets, ignoreNodes, ignoreChannels, hops)
|
||||
|
||||
case Event(Status.Failure(t), WaitingForRoute(s, c, failures)) =>
|
||||
|
@ -181,16 +176,16 @@ object PaymentLifecycle {
|
|||
* - firstExpiry is the cltv expiry for the first htlc in the route
|
||||
* - a sequence of payloads that will be used to build the onion
|
||||
*/
|
||||
def buildPayloads(finalAmountMsat: Long, finalExpiry: Int, hops: Seq[PaymentHop]): (Long, Int, Seq[PerHopPayload]) =
|
||||
def buildPayloads(finalAmountMsat: Long, finalExpiry: Long, hops: Seq[PaymentHop]): (Long, Long, Seq[PerHopPayload]) =
|
||||
hops.reverse.foldLeft((finalAmountMsat, finalExpiry, PerHopPayload(0L, finalAmountMsat, finalExpiry) :: Nil)) {
|
||||
case ((msat, expiry, payloads), hop) =>
|
||||
(msat + hop.nextFee(msat), expiry + hop.cltvExpiryDelta, PerHopPayload(hop.shortChannelId, msat, expiry) +: payloads)
|
||||
}
|
||||
|
||||
// this is defined in BOLT 11
|
||||
val defaultMinFinalCltvExpiry = 9
|
||||
val defaultMinFinalCltvExpiry:Long = 9L
|
||||
|
||||
def buildCommand(finalAmountMsat: Long, finalExpiry: Int, paymentHash: BinaryData, hops: Seq[Hop]): (CMD_ADD_HTLC, Seq[(BinaryData, PublicKey)]) = {
|
||||
def buildCommand(finalAmountMsat: Long, finalExpiry: Long, paymentHash: BinaryData, hops: Seq[Hop]): (CMD_ADD_HTLC, Seq[(BinaryData, PublicKey)]) = {
|
||||
val (firstAmountMsat, firstExpiry, payloads) = buildPayloads(finalAmountMsat, finalExpiry, hops.drop(1))
|
||||
val nodes = hops.map(_.nextNodeId)
|
||||
// BOLT 2 requires that associatedData == paymentHash
|
||||
|
|
|
@ -20,7 +20,6 @@ case class Relayed(originChannelId: BinaryData, originHtlcId: Long, amountMsatIn
|
|||
|
||||
case class ForwardAdd(add: UpdateAddHtlc)
|
||||
case class ForwardFulfill(fulfill: UpdateFulfillHtlc, to: Origin)
|
||||
case class ForwardLocalFail(error: Throwable, to: Origin) // happens when the failure happened in a local channel (and not in some downstream channel)
|
||||
case class ForwardFail(fail: UpdateFailHtlc, to: Origin)
|
||||
case class ForwardFailMalformed(fail: UpdateFailMalformedHtlc, to: Origin)
|
||||
|
||||
|
@ -45,7 +44,7 @@ class Relayer(nodeParams: NodeParams, register: ActorRef, paymentHandler: ActorR
|
|||
case ChannelStateChanged(channel, _, _, _, NORMAL | SHUTDOWN | CLOSING, d: HasCommitments) =>
|
||||
import d.channelId
|
||||
preimagesDb.listPreimages(channelId) match {
|
||||
case Nil => {}
|
||||
case Nil => ()
|
||||
case preimages =>
|
||||
log.info(s"re-sending ${preimages.size} unacked fulfills to channel $channelId")
|
||||
preimages.map(p => CMD_FULFILL_HTLC(p._2, p._3, commit = false)).foreach(channel ! _)
|
||||
|
@ -75,11 +74,11 @@ class Relayer(nodeParams: NodeParams, register: ActorRef, paymentHandler: ActorR
|
|||
paymentHandler forward add
|
||||
}
|
||||
case Success((Attempt.Successful(DecodeResult(perHopPayload, _)), nextPacket, _)) =>
|
||||
val channelUpdate_opt = channelUpdates.get(perHopPayload.channel_id)
|
||||
channelUpdate_opt match {
|
||||
channelUpdates.get(perHopPayload.channel_id) match {
|
||||
case None =>
|
||||
// TODO: clarify what we're supposed to do in the specs
|
||||
sender ! CMD_FAIL_HTLC(add.id, Right(TemporaryNodeFailure), commit = true)
|
||||
// if we don't (yet?) have a channel_update for the next channel, we consider the channel doesn't exist
|
||||
// TODO: use a different channel to the same peer instead?
|
||||
sender ! CMD_FAIL_HTLC(add.id, Right(UnknownNextPeer), commit = true)
|
||||
case Some(channelUpdate) if !Announcements.isEnabled(channelUpdate.flags) =>
|
||||
sender ! CMD_FAIL_HTLC(add.id, Right(ChannelDisabled(channelUpdate.flags, channelUpdate)), commit = true)
|
||||
case Some(channelUpdate) if add.amountMsat < channelUpdate.htlcMinimumMsat =>
|
||||
|
@ -90,7 +89,7 @@ class Relayer(nodeParams: NodeParams, register: ActorRef, paymentHandler: ActorR
|
|||
sender ! CMD_FAIL_HTLC(add.id, Right(ExpiryTooSoon(channelUpdate)), commit = true)
|
||||
case _ =>
|
||||
log.info(s"forwarding htlc #${add.id} to shortChannelId=${perHopPayload.channel_id}")
|
||||
register forward Register.ForwardShortId(perHopPayload.channel_id, CMD_ADD_HTLC(perHopPayload.amtToForward, add.paymentHash, perHopPayload.outgoingCltvValue, nextPacket.serialize, upstream_opt = Some(add), commit = true))
|
||||
register ! Register.ForwardShortId(perHopPayload.channel_id, CMD_ADD_HTLC(perHopPayload.amtToForward, add.paymentHash, perHopPayload.outgoingCltvValue, nextPacket.serialize, upstream_opt = Some(add), commit = true))
|
||||
}
|
||||
case Success((Attempt.Failure(cause), _, _)) =>
|
||||
log.error(s"couldn't parse payload: $cause")
|
||||
|
@ -105,6 +104,20 @@ class Relayer(nodeParams: NodeParams, register: ActorRef, paymentHandler: ActorR
|
|||
log.warning(s"couldn't resolve downstream channel $shortChannelId, failing htlc #${add.id}")
|
||||
register ! Register.Forward(add.channelId, CMD_FAIL_HTLC(add.id, Right(UnknownNextPeer), commit = true))
|
||||
|
||||
case AddHtlcFailed(_, error, Local(Some(sender)), _) =>
|
||||
sender ! Status.Failure(error)
|
||||
|
||||
case AddHtlcFailed(_, error, Relayed(originChannelId, originHtlcId, _, _), channelUpdate_opt) =>
|
||||
val failure = (error, channelUpdate_opt) match {
|
||||
case (_: ChannelUnavailable, Some(channelUpdate)) if !Announcements.isEnabled(channelUpdate.flags) => ChannelDisabled(channelUpdate.flags, channelUpdate)
|
||||
case (_: InsufficientFunds, Some(channelUpdate)) => TemporaryChannelFailure(channelUpdate)
|
||||
case (_: TooManyAcceptedHtlcs, Some(channelUpdate)) => TemporaryChannelFailure(channelUpdate)
|
||||
case (_: HtlcTimedout, _) => PermanentChannelFailure
|
||||
case _ => TemporaryNodeFailure
|
||||
}
|
||||
val cmd = CMD_FAIL_HTLC(originHtlcId, Right(failure), commit = true)
|
||||
register ! Register.Forward(originChannelId, cmd)
|
||||
|
||||
case ForwardFulfill(fulfill, Local(Some(sender))) =>
|
||||
sender ! fulfill
|
||||
|
||||
|
@ -119,18 +132,6 @@ class Relayer(nodeParams: NodeParams, register: ActorRef, paymentHandler: ActorR
|
|||
log.debug(s"fulfill acked for channelId=$channelId htlcId=$htlcId")
|
||||
preimagesDb.removePreimage(channelId, htlcId)
|
||||
|
||||
case ForwardLocalFail(error, Local(Some(sender))) =>
|
||||
sender ! Status.Failure(error)
|
||||
|
||||
case ForwardLocalFail(error, Relayed(originChannelId, originHtlcId, _, _)) =>
|
||||
// TODO: clarify what we're supposed to do in the specs depending on the error
|
||||
val failure = error match {
|
||||
case HtlcTimedout(_) => PermanentChannelFailure
|
||||
case _ => TemporaryNodeFailure
|
||||
}
|
||||
val cmd = CMD_FAIL_HTLC(originHtlcId, Right(failure), commit = true)
|
||||
register ! Register.Forward(originChannelId, cmd)
|
||||
|
||||
case ForwardFail(fail, Local(Some(sender))) =>
|
||||
sender ! fail
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ case class Data(nodes: Map[PublicKey, NodeAnnouncement],
|
|||
stash: Seq[RoutingMessage],
|
||||
awaiting: Seq[ChannelAnnouncement],
|
||||
origins: Map[RoutingMessage, ActorRef],
|
||||
localChannels: Map[BinaryData, PublicKey],
|
||||
localUpdates: Map[BinaryData, (ChannelDesc, ChannelUpdate)],
|
||||
excludedChannels: Set[ChannelDesc]) // those channels are temporarily excluded from route calculation, because their node returned a TemporaryChannelFailure
|
||||
|
||||
sealed trait State
|
||||
|
@ -101,7 +101,7 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSM[State, Data]
|
|||
|
||||
when(WAITING_FOR_VALIDATION) {
|
||||
case Event(ParallelGetResponse(results), d) =>
|
||||
val validated = results.map {
|
||||
val validated = results.flatMap {
|
||||
case IndividualResult(c, Some(tx), true) =>
|
||||
// TODO: blacklisting
|
||||
val (_, _, outputIndex) = fromShortId(c.shortChannelId)
|
||||
|
@ -133,8 +133,18 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSM[State, Data]
|
|||
// TODO: blacklist?
|
||||
log.warning(s"could not retrieve tx for shortChannelId=${c.shortChannelId}")
|
||||
None
|
||||
}.flatten
|
||||
// we reprocess node and channel-update announcements that may have been validated
|
||||
}
|
||||
|
||||
// in case we just validated our first local channel, we announce the local node
|
||||
// note that this will also make sure we always update our node announcement on restart (eg: alias, color), because
|
||||
// even if we had stored a previous announcement, it would be overriden by this more recent one
|
||||
if (!d.nodes.contains(nodeParams.nodeId) && validated.exists(isRelatedTo(_, nodeParams.nodeId))) {
|
||||
log.info(s"first local channel validated, announcing local node")
|
||||
val nodeAnn = Announcements.makeNodeAnnouncement(nodeParams.privateKey, nodeParams.alias, nodeParams.color, nodeParams.publicAddresses)
|
||||
self ! nodeAnn
|
||||
}
|
||||
|
||||
// we also reprocess node and channel_update announcements related to channels that were processed
|
||||
val (resend, stash1) = d.stash.partition {
|
||||
case n: NodeAnnouncement => results.exists(r => isRelatedTo(r.c, n.nodeId))
|
||||
case u: ChannelUpdate => results.exists(r => r.c.shortChannelId == u.shortChannelId)
|
||||
|
@ -146,10 +156,13 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSM[State, Data]
|
|||
|
||||
whenUnhandled {
|
||||
case Event(ChannelStateChanged(_, _, _, _, channel.NORMAL, d: DATA_NORMAL), d1) =>
|
||||
stay using d1.copy(localChannels = d1.localChannels + (d.commitments.channelId -> d.commitments.remoteParams.nodeId))
|
||||
val channelDesc = ChannelDesc(d.channelUpdate.shortChannelId, d.commitments.localParams.nodeId, d.commitments.remoteParams.nodeId)
|
||||
log.debug(s"added local channel_update for channelId=${d.channelId} update=${d.channelUpdate}")
|
||||
stay using d1.copy(localUpdates = d1.localUpdates + (d.channelId -> (channelDesc, d.channelUpdate)))
|
||||
|
||||
case Event(ChannelStateChanged(_, _, _, channel.NORMAL, _, d: DATA_NEGOTIATING), d1) =>
|
||||
stay using d1.copy(localChannels = d1.localChannels - d.commitments.channelId)
|
||||
case Event(ChannelStateChanged(_, _, _, channel.NORMAL, _, d: HasCommitments), d1) =>
|
||||
log.debug(s"removed local channel_update for channelId=${d.channelId}")
|
||||
stay using d1.copy(localUpdates = d1.localUpdates - d.channelId)
|
||||
|
||||
case Event(_: ChannelStateChanged, _) => stay
|
||||
|
||||
|
@ -313,21 +326,13 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSM[State, Data]
|
|||
stay
|
||||
|
||||
case Event(RouteRequest(start, end, ignoreNodes, ignoreChannels), d) =>
|
||||
val localNodeId = nodeParams.privateKey.publicKey
|
||||
// TODO: HACK!!!!! the following is a workaround to make our routing work with private/not-yet-announced channels, that do not have a channelUpdate
|
||||
val fakeUpdates = d.localChannels.map { case (channelId, remoteNodeId) =>
|
||||
// note that this id is deterministic, otherwise filterUpdates would not work
|
||||
val fakeShortId = BigInt(channelId.take(7).toArray).toLong
|
||||
val channelDesc = ChannelDesc(fakeShortId, localNodeId, remoteNodeId)
|
||||
// note that we store the channelId in the sig, other values are not used because if it is selected this will be the first channel in the route
|
||||
val channelUpdate = ChannelUpdate(signature = channelId, chainHash = nodeParams.chainHash, fakeShortId, 0, "0000", 0, 0, 0, 0)
|
||||
(channelDesc -> channelUpdate)
|
||||
}
|
||||
// we replace local channelUpdates (we have them for regular public already-announced channels) by the ones we just generated
|
||||
val updates1 = d.updates.filterKeys(_.a != localNodeId) ++ fakeUpdates
|
||||
// we start with channel_updates of local channels
|
||||
val updates0 = d.localUpdates.values.toMap
|
||||
// we add them to the publicly-announced updates (channel_updates for announced channels will be deduped)
|
||||
val updates1 = d.updates ++ updates0
|
||||
// we then filter out the currently excluded channels
|
||||
val updates2 = updates1.filterKeys(!d.excludedChannels.contains(_))
|
||||
// we also filter out excluded channels
|
||||
// we also filter out disabled channels, and channels/nodes that are blacklisted for this particular request
|
||||
val updates3 = filterUpdates(updates2, ignoreNodes, ignoreChannels)
|
||||
log.info(s"finding a route $start->$end with ignoreNodes=${ignoreNodes.map(_.toBin).mkString(",")} ignoreChannels=${ignoreChannels.map(_.toHexString).mkString(",")}")
|
||||
findRoute(start, end, updates3).map(r => RouteResponse(r, ignoreNodes, ignoreChannels)) pipeTo sender
|
||||
|
@ -380,7 +385,7 @@ object Router {
|
|||
updates
|
||||
.filterNot(u => ignoreNodes.map(_.toBin).contains(u._1.a) || ignoreNodes.map(_.toBin).contains(u._1.b))
|
||||
.filterNot(u => ignoreChannels.contains(u._1.id))
|
||||
.filterNot(u => !Announcements.isEnabled(u._2.flags))
|
||||
.filter(u => Announcements.isEnabled(u._2.flags))
|
||||
|
||||
def findRouteDijkstra(localNodeId: PublicKey, targetNodeId: PublicKey, channels: Iterable[ChannelDesc]): Seq[ChannelDesc] = {
|
||||
if (localNodeId == targetNodeId) throw CannotRouteToSelf
|
||||
|
|
|
@ -377,9 +377,6 @@ object Transactions {
|
|||
closingTx.copy(tx = closingTx.tx.updateWitness(0, witness))
|
||||
}
|
||||
|
||||
def checkSpendable(parent: Transaction, child: Transaction): Try[Unit] =
|
||||
Try(Transaction.correctlySpends(child, parent :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS))
|
||||
|
||||
def checkSpendable(txinfo: TransactionWithInputInfo): Try[Unit] =
|
||||
Try(Transaction.correctlySpends(txinfo.tx, Map(txinfo.tx.txIn(0).outPoint -> txinfo.input.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS))
|
||||
|
||||
|
|
|
@ -196,11 +196,14 @@ object ChannelCodecs {
|
|||
|
||||
val DATA_WAIT_FOR_FUNDING_LOCKED_Codec: Codec[DATA_WAIT_FOR_FUNDING_LOCKED] = (
|
||||
("commitments" | commitmentsCodec) ::
|
||||
("shortChannelId" | uint64) ::
|
||||
("lastSent" | fundingLockedCodec)).as[DATA_WAIT_FOR_FUNDING_LOCKED]
|
||||
|
||||
val DATA_NORMAL_Codec: Codec[DATA_NORMAL] = (
|
||||
("commitments" | commitmentsCodec) ::
|
||||
("shortChannelId" | optional(bool, uint64)) ::
|
||||
("shortChannelId" | uint64) ::
|
||||
("channelAnnouncement" | optional(bool, channelAnnouncementCodec)) ::
|
||||
("channelUpdate" | channelUpdateCodec) ::
|
||||
("localAnnouncementSignatures" | optional(bool, announcementSignaturesCodec)) ::
|
||||
("localShutdown" | optional(bool, shutdownCodec)) ::
|
||||
("remoteShutdown" | optional(bool, shutdownCodec))).as[DATA_NORMAL]
|
||||
|
@ -214,11 +217,12 @@ object ChannelCodecs {
|
|||
("commitments" | commitmentsCodec) ::
|
||||
("localShutdown" | shutdownCodec) ::
|
||||
("remoteShutdown" | shutdownCodec) ::
|
||||
("localClosingSigned" | closingSignedCodec)).as[DATA_NEGOTIATING]
|
||||
("localClosingSigned" | listOfN(uint16, closingSignedCodec))).as[DATA_NEGOTIATING]
|
||||
|
||||
val DATA_CLOSING_Codec: Codec[DATA_CLOSING] = (
|
||||
("commitments" | commitmentsCodec) ::
|
||||
("mutualClosePublished" | optional(bool, txCodec)) ::
|
||||
("localClosingSigned" | listOfN(uint16, closingSignedCodec)) ::
|
||||
("mutualClosePublished" | listOfN(uint16, txCodec)) ::
|
||||
("localCommitPublished" | optional(bool, localCommitPublishedCodec)) ::
|
||||
("remoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) ::
|
||||
("nextRemoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) ::
|
||||
|
|
|
@ -294,7 +294,7 @@ object LightningMessageCodecs {
|
|||
("realm" | constant(ByteVector.fromByte(0))) ::
|
||||
("channel_id" | uint64) ::
|
||||
("amt_to_forward" | uint64) ::
|
||||
("outgoing_cltv_value" | int32) :: // we use a signed int32, it is enough to store cltv for 40 000 years
|
||||
("outgoing_cltv_value" | uint32) ::
|
||||
("unused_with_v0_version_on_header" | ignore(8 * 12))).as[PerHopPayload]
|
||||
|
||||
}
|
||||
|
|
|
@ -157,4 +157,4 @@ case class ChannelUpdate(signature: BinaryData,
|
|||
|
||||
case class PerHopPayload(channel_id: Long,
|
||||
amtToForward: Long,
|
||||
outgoingCltvValue: Int)
|
||||
outgoingCltvValue: Long)
|
11
eclair-core/src/test/resources/application.conf
Normal file
11
eclair-core/src/test/resources/application.conf
Normal file
|
@ -0,0 +1,11 @@
|
|||
akka {
|
||||
loggers = ["akka.event.slf4j.Slf4jLogger"]
|
||||
loglevel = "DEBUG"
|
||||
|
||||
actor {
|
||||
debug {
|
||||
# enable DEBUG logging of all LoggingFSMs for events, transitions and timers
|
||||
fsm = on
|
||||
}
|
||||
}
|
||||
}
|
|
@ -77,6 +77,8 @@ trait StateTestsHelperMethods extends TestKitBase {
|
|||
alice2bob.forward(bob)
|
||||
bob2alice.expectMsgType[FundingLocked]
|
||||
bob2alice.forward(alice)
|
||||
alice2blockchain.expectMsgType[WatchConfirmed] // deeply buried
|
||||
bob2blockchain.expectMsgType[WatchConfirmed] // deeply buried
|
||||
awaitCond(alice.stateName == NORMAL)
|
||||
awaitCond(bob.stateName == NORMAL)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package fr.acinq.eclair.channel.states.c
|
||||
|
||||
import akka.testkit.{TestFSMRef, TestProbe}
|
||||
import fr.acinq.bitcoin.Transaction
|
||||
import fr.acinq.eclair.TestConstants.{Alice, Bob}
|
||||
import fr.acinq.eclair.blockchain._
|
||||
import fr.acinq.eclair.channel._
|
||||
|
@ -85,7 +86,7 @@ class WaitForFundingConfirmedStateSpec extends TestkitBaseClass with StateTestsH
|
|||
test("recv BITCOIN_FUNDING_SPENT (other commit)") { case (alice, _, alice2bob, _, alice2blockchain) =>
|
||||
within(30 seconds) {
|
||||
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, null)
|
||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, Transaction(0, Nil, Nil, 0))
|
||||
alice2bob.expectMsgType[Error]
|
||||
alice2blockchain.expectMsg(PublishAsap(tx))
|
||||
awaitCond(alice.stateName == ERR_INFORMATION_LEAK)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package fr.acinq.eclair.channel.states.c
|
||||
|
||||
import akka.testkit.{TestFSMRef, TestProbe}
|
||||
import fr.acinq.bitcoin.Transaction
|
||||
import fr.acinq.eclair.TestConstants.{Alice, Bob}
|
||||
import fr.acinq.eclair.blockchain._
|
||||
import fr.acinq.eclair.channel._
|
||||
|
@ -71,10 +72,10 @@ class WaitForFundingLockedStateSpec extends TestkitBaseClass with StateTestsHelp
|
|||
}
|
||||
}
|
||||
|
||||
test("recv BITCOIN_FUNDING_SPENT (other commit)") { case (alice, _, alice2bob, bob2alice, alice2blockchain, router) =>
|
||||
test("recv BITCOIN_FUNDING_SPENT (other commit)") { case (alice, _, alice2bob, _, alice2blockchain, _) =>
|
||||
within(30 seconds) {
|
||||
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, null)
|
||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, Transaction(0, Nil, Nil, 0))
|
||||
alice2bob.expectMsgType[Error]
|
||||
alice2blockchain.expectMsg(PublishAsap(tx))
|
||||
alice2blockchain.expectMsgType[PublishAsap]
|
||||
|
@ -82,7 +83,7 @@ class WaitForFundingLockedStateSpec extends TestkitBaseClass with StateTestsHelp
|
|||
}
|
||||
}
|
||||
|
||||
test("recv Error") { case (alice, _, alice2bob, bob2alice, alice2blockchain, router) =>
|
||||
test("recv Error") { case (alice, _, _, _, alice2blockchain, _) =>
|
||||
within(30 seconds) {
|
||||
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
alice ! Error("00" * 32, "oops".getBytes)
|
||||
|
|
|
@ -4,15 +4,16 @@ 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}
|
||||
import fr.acinq.eclair.TestConstants.Bob
|
||||
import fr.acinq.eclair.TestConstants.{Alice, Bob}
|
||||
import fr.acinq.eclair.UInt64.Conversions._
|
||||
import fr.acinq.eclair.blockchain._
|
||||
import fr.acinq.eclair.blockchain.fee.FeeratesPerKw
|
||||
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
||||
import fr.acinq.eclair.channel.{Data, State, _}
|
||||
import fr.acinq.eclair.payment._
|
||||
import fr.acinq.eclair.router.Announcements
|
||||
import fr.acinq.eclair.transactions.{IN, OUT}
|
||||
import fr.acinq.eclair.wire.{AnnouncementSignatures, ClosingSigned, CommitSig, Error, FailureMessageCodecs, PermanentChannelFailure, RevokeAndAck, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFee, UpdateFulfillHtlc}
|
||||
import fr.acinq.eclair.wire.{AnnouncementSignatures, ChannelUpdate, ClosingSigned, CommitSig, Error, FailureMessageCodecs, PermanentChannelFailure, RevokeAndAck, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFee, UpdateFulfillHtlc}
|
||||
import fr.acinq.eclair.{Globals, TestConstants, TestkitBaseClass}
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.Tag
|
||||
|
@ -33,6 +34,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
import setup._
|
||||
within(30 seconds) {
|
||||
reachNormal(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, test.tags)
|
||||
relayer.expectMsgType[ChannelUpdate]
|
||||
relayer.expectMsgType[ChannelUpdate]
|
||||
awaitCond(alice.stateName == NORMAL)
|
||||
awaitCond(bob.stateName == NORMAL)
|
||||
}
|
||||
|
@ -90,14 +93,14 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CMD_ADD_HTLC (invalid payment hash)") { case (alice, _, alice2bob, _, _, _, relayer) =>
|
||||
test("recv CMD_ADD_HTLC (invalid payment hash)") { case (alice, _, alice2bob, _, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
|
||||
val sender = TestProbe()
|
||||
val add = CMD_ADD_HTLC(500000000, "11" * 42, expiry = 400144)
|
||||
sender.send(alice, add)
|
||||
val error = InvalidPaymentHash(channelId(alice))
|
||||
//sender.expectMsg(Failure(InvalidPaymentHash(channelId(alice))))
|
||||
relayer.expectMsg(ForwardLocalFail(error, Local(Some(sender.ref))))
|
||||
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), error, Local(Some(sender.ref)), Some(initialState.channelUpdate))))
|
||||
alice2bob.expectNoMsg(200 millis)
|
||||
}
|
||||
}
|
||||
|
@ -105,11 +108,11 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
test("recv CMD_ADD_HTLC (expiry too small)") { case (alice, _, alice2bob, _, _, _, relayer) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
|
||||
val add = CMD_ADD_HTLC(500000000, "11" * 32, expiry = 300000)
|
||||
sender.send(alice, add)
|
||||
val error = ExpiryCannotBeInThePast(channelId(alice), 300000, 400000)
|
||||
//sender.expectMsg(Failure(error))
|
||||
relayer.expectMsg(ForwardLocalFail(error, Local(Some(sender.ref))))
|
||||
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), error, Local(Some(sender.ref)), Some(initialState.channelUpdate))))
|
||||
alice2bob.expectNoMsg(200 millis)
|
||||
}
|
||||
}
|
||||
|
@ -117,11 +120,11 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
test("recv CMD_ADD_HTLC (value too small)") { case (alice, _, alice2bob, _, _, _, relayer) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
|
||||
val add = CMD_ADD_HTLC(50, "11" * 32, 400144)
|
||||
sender.send(alice, add)
|
||||
val error = HtlcValueTooSmall(channelId(alice), 1000, 50)
|
||||
//sender.expectMsg(Failure(error))
|
||||
relayer.expectMsg(ForwardLocalFail(error, Local(Some(sender.ref))))
|
||||
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), error, Local(Some(sender.ref)), Some(initialState.channelUpdate))))
|
||||
alice2bob.expectNoMsg(200 millis)
|
||||
}
|
||||
}
|
||||
|
@ -129,11 +132,11 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
test("recv CMD_ADD_HTLC (insufficient funds)") { case (alice, _, alice2bob, _, _, _, relayer) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
|
||||
val add = CMD_ADD_HTLC(Int.MaxValue, "11" * 32, 400144)
|
||||
sender.send(alice, add)
|
||||
val error = InsufficientFunds(channelId(alice), amountMsat = Int.MaxValue, missingSatoshis = 1376443, reserveSatoshis = 20000, feesSatoshis = 8960)
|
||||
//sender.expectMsg(Failure(error))
|
||||
relayer.expectMsg(ForwardLocalFail(error, Local(Some(sender.ref))))
|
||||
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), error, Local(Some(sender.ref)), Some(initialState.channelUpdate))))
|
||||
alice2bob.expectNoMsg(200 millis)
|
||||
}
|
||||
}
|
||||
|
@ -141,6 +144,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
test("recv CMD_ADD_HTLC (insufficient funds w/ pending htlcs and 0 balance)") { case (alice, _, alice2bob, _, _, _, relayer) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
|
||||
sender.send(alice, CMD_ADD_HTLC(500000000, "11" * 32, 400144))
|
||||
sender.expectMsg("ok")
|
||||
alice2bob.expectMsgType[UpdateAddHtlc]
|
||||
|
@ -153,8 +157,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
val add = CMD_ADD_HTLC(1000000, "44" * 32, 400144)
|
||||
sender.send(alice, add)
|
||||
val error = InsufficientFunds(channelId(alice), amountMsat = 1000000, missingSatoshis = 1000, reserveSatoshis = 20000, feesSatoshis = 12400)
|
||||
//sender.expectMsg(Failure(error))
|
||||
relayer.expectMsg(ForwardLocalFail(error, Local(Some(sender.ref))))
|
||||
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), error, Local(Some(sender.ref)), Some(initialState.channelUpdate))))
|
||||
alice2bob.expectNoMsg(200 millis)
|
||||
}
|
||||
}
|
||||
|
@ -162,6 +165,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
test("recv CMD_ADD_HTLC (insufficient funds w/ pending htlcs 2/2)") { case (alice, _, alice2bob, _, _, _, relayer) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
|
||||
sender.send(alice, CMD_ADD_HTLC(300000000, "11" * 32, 400144))
|
||||
sender.expectMsg("ok")
|
||||
alice2bob.expectMsgType[UpdateAddHtlc]
|
||||
|
@ -171,8 +175,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
val add = CMD_ADD_HTLC(500000000, "33" * 32, 400144)
|
||||
sender.send(alice, add)
|
||||
val error = InsufficientFunds(channelId(alice), amountMsat = 500000000, missingSatoshis = 332400, reserveSatoshis = 20000, feesSatoshis = 12400)
|
||||
//sender.expectMsg(Failure(error))
|
||||
relayer.expectMsg(ForwardLocalFail(error, Local(Some(sender.ref))))
|
||||
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), error, Local(Some(sender.ref)), Some(initialState.channelUpdate))))
|
||||
alice2bob.expectNoMsg(200 millis)
|
||||
}
|
||||
}
|
||||
|
@ -180,11 +183,11 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
test("recv CMD_ADD_HTLC (over max inflight htlc value)") { case (_, bob, _, bob2alice, _, _, relayer) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
|
||||
val add = CMD_ADD_HTLC(151000000, "11" * 32, 400144)
|
||||
sender.send(bob, add)
|
||||
val error = HtlcValueTooHighInFlight(channelId(bob), maximum = 150000000, actual = 151000000)
|
||||
//sender.expectMsg(Failure(error))
|
||||
relayer.expectMsg(ForwardLocalFail(error, Local(Some(sender.ref))))
|
||||
sender.expectMsg(Failure(AddHtlcFailed(channelId(bob), error, Local(Some(sender.ref)), Some(initialState.channelUpdate))))
|
||||
bob2alice.expectNoMsg(200 millis)
|
||||
}
|
||||
}
|
||||
|
@ -192,6 +195,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
test("recv CMD_ADD_HTLC (over max accepted htlcs)") { case (alice, _, alice2bob, _, _, _, relayer) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
|
||||
// Bob accepts a maximum of 30 htlcs
|
||||
for (i <- 0 until 30) {
|
||||
sender.send(alice, CMD_ADD_HTLC(10000000, "11" * 32, 400144))
|
||||
|
@ -201,15 +205,15 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
val add = CMD_ADD_HTLC(10000000, "33" * 32, 400144)
|
||||
sender.send(alice, add)
|
||||
val error = TooManyAcceptedHtlcs(channelId(alice), maximum = 30)
|
||||
//sender.expectMsg(Failure(error))
|
||||
relayer.expectMsg(ForwardLocalFail(error, Local(Some(sender.ref))))
|
||||
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), error, Local(Some(sender.ref)), Some(initialState.channelUpdate))))
|
||||
alice2bob.expectNoMsg(200 millis)
|
||||
}
|
||||
}
|
||||
|
||||
test("recv CMD_ADD_HTLC (while waiting for a revocation)") { case (alice, _, alice2bob, _, _, _, relayer) =>
|
||||
test("recv CMD_ADD_HTLC (over capacity)") { case (alice, _, alice2bob, _, _, _, relayer) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
|
||||
val add1 = CMD_ADD_HTLC(TestConstants.fundingSatoshis * 2 / 3 * 1000, "11" * 32, 400144)
|
||||
sender.send(alice, add1)
|
||||
sender.expectMsg("ok")
|
||||
|
@ -220,8 +224,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
// this is over channel-capacity
|
||||
val add2 = CMD_ADD_HTLC(TestConstants.fundingSatoshis * 2 / 3 * 1000, "22" * 32, 400144)
|
||||
sender.send(alice, add2)
|
||||
//sender.expectMsgType[Failure]
|
||||
relayer.expectMsgType[ForwardLocalFail]
|
||||
val error = InsufficientFunds(channelId(alice), add2.amountMsat, 564012, 20000, 10680)
|
||||
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), error, Local(Some(sender.ref)), Some(initialState.channelUpdate))))
|
||||
alice2bob.expectNoMsg(200 millis)
|
||||
}
|
||||
}
|
||||
|
@ -229,6 +233,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
test("recv CMD_ADD_HTLC (after having sent Shutdown)") { case (alice, _, alice2bob, _, _, _, relayer) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
|
||||
sender.send(alice, CMD_CLOSE(None))
|
||||
sender.expectMsg("ok")
|
||||
alice2bob.expectMsgType[Shutdown]
|
||||
|
@ -237,9 +242,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
// actual test starts here
|
||||
val add = CMD_ADD_HTLC(500000000, "11" * 32, expiry = 400144)
|
||||
sender.send(alice, add)
|
||||
val error = ClosingInProgress(channelId(alice))
|
||||
//sender.expectMsg(Failure(error))
|
||||
relayer.expectMsg(ForwardLocalFail(error, Local(Some(sender.ref))))
|
||||
val error = NoMoreHtlcsClosingInProgress(channelId(alice))
|
||||
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), error, Local(Some(sender.ref)), Some(initialState.channelUpdate))))
|
||||
alice2bob.expectNoMsg(200 millis)
|
||||
}
|
||||
}
|
||||
|
@ -247,6 +251,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
test("recv CMD_ADD_HTLC (after having received Shutdown)") { case (alice, bob, alice2bob, bob2alice, _, _, relayer) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
|
||||
// let's make alice send an htlc
|
||||
val add1 = CMD_ADD_HTLC(500000000, "11" * 32, expiry = 400144)
|
||||
sender.send(alice, add1)
|
||||
|
@ -262,9 +267,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
bob2alice.expectMsgType[Shutdown]
|
||||
bob2alice.forward(alice)
|
||||
sender.send(alice, add2)
|
||||
val error = ClosingInProgress(channelId(alice))
|
||||
//sender.expectMsg(Failure(error))
|
||||
relayer.expectMsg(ForwardLocalFail(error, Local(Some(sender.ref))))
|
||||
val error = NoMoreHtlcsClosingInProgress(channelId(alice))
|
||||
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), error, Local(Some(sender.ref)), Some(initialState.channelUpdate))))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -424,7 +428,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CMD_SIGN (no changes)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) =>
|
||||
test("recv CMD_SIGN (no changes)") { case (alice, _, _, _, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
sender.send(alice, CMD_SIGN)
|
||||
|
@ -433,7 +437,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CMD_SIGN (while waiting for RevokeAndAck (no pending changes)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) =>
|
||||
test("recv CMD_SIGN (while waiting for RevokeAndAck (no pending changes)") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
|
@ -452,7 +456,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CMD_SIGN (while waiting for RevokeAndAck (with pending changes)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) =>
|
||||
test("recv CMD_SIGN (while waiting for RevokeAndAck (with pending changes)") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val (r1, htlc1) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
|
@ -556,6 +560,25 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CommitSig (only fee update)") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
|
||||
sender.send(alice, CMD_UPDATE_FEE(TestConstants.feeratePerKw + 1000, commit = false))
|
||||
sender.expectMsg("ok")
|
||||
sender.send(alice, CMD_SIGN)
|
||||
sender.expectMsg("ok")
|
||||
|
||||
// actual test begins (note that channel sends a CMD_SIGN to itself when it receives RevokeAndAck and there are changes)
|
||||
alice2bob.expectMsgType[UpdateFee]
|
||||
alice2bob.forward(bob)
|
||||
alice2bob.expectMsgType[CommitSig]
|
||||
alice2bob.forward(bob)
|
||||
bob2alice.expectMsgType[RevokeAndAck]
|
||||
bob2alice.forward(alice)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: maybe should be illegal?
|
||||
ignore("recv CommitSig (two htlcs received with same r)") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
|
@ -606,15 +629,60 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
|
||||
// actual test begins
|
||||
sender.send(bob, CommitSig("00" * 32, "00" * 64, Nil))
|
||||
bob2alice.expectMsgType[Error]
|
||||
awaitCond(bob.stateName == CLOSING)
|
||||
val error = bob2alice.expectMsgType[Error]
|
||||
assert(new String(error.data).startsWith("invalid commitment signature"))
|
||||
bob2blockchain.expectMsg(PublishAsap(tx))
|
||||
bob2blockchain.expectMsgType[PublishAsap]
|
||||
bob2blockchain.expectMsgType[WatchConfirmed]
|
||||
}
|
||||
}
|
||||
|
||||
test("recv RevokeAndAck (one htlc sent)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, _) =>
|
||||
test("recv CommitSig (bad htlc sig count)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
|
||||
sender.send(alice, CMD_SIGN)
|
||||
sender.expectMsg("ok")
|
||||
val commitSig = alice2bob.expectMsgType[CommitSig]
|
||||
|
||||
// actual test begins
|
||||
val badCommitSig = commitSig.copy(htlcSignatures = commitSig.htlcSignatures ::: commitSig.htlcSignatures)
|
||||
sender.send(bob, badCommitSig)
|
||||
val error = bob2alice.expectMsgType[Error]
|
||||
assert(new String(error.data) === HtlcSigCountMismatch(channelId(bob), expected = 1, actual = 2).getMessage)
|
||||
bob2blockchain.expectMsg(PublishAsap(tx))
|
||||
bob2blockchain.expectMsgType[PublishAsap]
|
||||
bob2blockchain.expectMsgType[WatchConfirmed]
|
||||
}
|
||||
}
|
||||
|
||||
test("recv CommitSig (invalid htlc sig)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
|
||||
sender.send(alice, CMD_SIGN)
|
||||
sender.expectMsg("ok")
|
||||
val commitSig = alice2bob.expectMsgType[CommitSig]
|
||||
|
||||
// actual test begins
|
||||
val badCommitSig = commitSig.copy(htlcSignatures = commitSig.signature :: Nil)
|
||||
sender.send(bob, badCommitSig)
|
||||
val error = bob2alice.expectMsgType[Error]
|
||||
assert(new String(error.data).startsWith("invalid htlc signature"))
|
||||
bob2blockchain.expectMsg(PublishAsap(tx))
|
||||
bob2blockchain.expectMsgType[PublishAsap]
|
||||
bob2blockchain.expectMsgType[WatchConfirmed]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
test("recv RevokeAndAck (one htlc sent)") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
|
@ -1646,7 +1714,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
val sender = TestProbe()
|
||||
sender.send(alice, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 42, 10))
|
||||
val annSigs = alice2bob.expectMsgType[AnnouncementSignatures]
|
||||
assert(alice.stateData.asInstanceOf[DATA_NORMAL] === initialState.copy(localAnnouncementSignatures = Some(annSigs)))
|
||||
assert(alice.stateData.asInstanceOf[DATA_NORMAL] === initialState.copy(shortChannelId = annSigs.shortChannelId, localAnnouncementSignatures = Some(annSigs)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1658,9 +1726,13 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
val annSigsA = alice2bob.expectMsgType[AnnouncementSignatures]
|
||||
sender.send(bob, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 42, 10))
|
||||
val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures]
|
||||
import initialState.commitments.localParams
|
||||
import initialState.commitments.remoteParams
|
||||
val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, localParams.nodeId, remoteParams.nodeId, localParams.fundingPrivKey.publicKey, remoteParams.fundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature)
|
||||
val channelUpdate = Announcements.makeChannelUpdate(Alice.nodeParams.chainHash, Alice.nodeParams.privateKey, remoteParams.nodeId, annSigsA.shortChannelId, Alice.nodeParams.expiryDeltaBlocks, Bob.nodeParams.htlcMinimumMsat, Alice.nodeParams.feeBaseMsat, Alice.nodeParams.feeProportionalMillionth)
|
||||
// actual test starts here
|
||||
bob2alice.forward(alice)
|
||||
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL] == initialState.copy(shortChannelId = Some(annSigsB.shortChannelId), localAnnouncementSignatures = Some(annSigsA)))
|
||||
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL] == initialState.copy(shortChannelId = annSigsA.shortChannelId, channelAnnouncement = Some(channelAnn), channelUpdate = channelUpdate, localAnnouncementSignatures = Some(annSigsA)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1672,8 +1744,12 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
val annSigsA = alice2bob.expectMsgType[AnnouncementSignatures]
|
||||
sender.send(bob, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 42, 10))
|
||||
val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures]
|
||||
import initialState.commitments.localParams
|
||||
import initialState.commitments.remoteParams
|
||||
val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, localParams.nodeId, remoteParams.nodeId, localParams.fundingPrivKey.publicKey, remoteParams.fundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature)
|
||||
val channelUpdate = Announcements.makeChannelUpdate(Alice.nodeParams.chainHash, Alice.nodeParams.privateKey, remoteParams.nodeId, annSigsA.shortChannelId, Alice.nodeParams.expiryDeltaBlocks, Bob.nodeParams.htlcMinimumMsat, Alice.nodeParams.feeBaseMsat, Alice.nodeParams.feeProportionalMillionth)
|
||||
bob2alice.forward(alice)
|
||||
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL] == initialState.copy(shortChannelId = Some(annSigsB.shortChannelId), localAnnouncementSignatures = Some(annSigsA)))
|
||||
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL] == initialState.copy(shortChannelId = annSigsA.shortChannelId, channelAnnouncement = Some(channelAnn), channelUpdate = channelUpdate, localAnnouncementSignatures = Some(annSigsA)))
|
||||
|
||||
// actual test starts here
|
||||
// simulate bob re-sending its sigs
|
||||
|
|
|
@ -8,8 +8,8 @@ import fr.acinq.eclair.blockchain._
|
|||
import fr.acinq.eclair.blockchain.fee.FeeratesPerKw
|
||||
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
||||
import fr.acinq.eclair.channel.{Data, State, _}
|
||||
import fr.acinq.eclair.payment.{ForwardAdd, ForwardLocalFail, Local, PaymentLifecycle, _}
|
||||
import fr.acinq.eclair.wire.{CommitSig, Error, FailureMessageCodecs, PermanentChannelFailure, RevokeAndAck, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFee, UpdateFulfillHtlc}
|
||||
import fr.acinq.eclair.payment.{ForwardAdd, Local, PaymentLifecycle, _}
|
||||
import fr.acinq.eclair.wire.{ChannelUpdate, CommitSig, Error, FailureMessageCodecs, PermanentChannelFailure, RevokeAndAck, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFee, UpdateFulfillHtlc}
|
||||
import fr.acinq.eclair.{Globals, TestConstants, TestkitBaseClass}
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
@ -29,6 +29,8 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
import setup._
|
||||
within(30 seconds) {
|
||||
reachNormal(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
||||
relayer.expectMsgType[ChannelUpdate]
|
||||
relayer.expectMsgType[ChannelUpdate]
|
||||
val sender = TestProbe()
|
||||
// alice sends an HTLC to bob
|
||||
val r1: BinaryData = "11" * 32
|
||||
|
@ -78,14 +80,13 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CMD_ADD_HTLC") { case (alice, _, alice2bob, _, _, _, relayer) =>
|
||||
test("recv CMD_ADD_HTLC") { case (alice, _, alice2bob, _, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val add = CMD_ADD_HTLC(500000000, "11" * 32, expiry = 300000)
|
||||
sender.send(alice, add)
|
||||
val error = ChannelUnavailable(channelId(alice))
|
||||
//sender.expectMsg(Failure(error))
|
||||
relayer.expectMsg(ForwardLocalFail(error, Local(Some(sender.ref))))
|
||||
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), error, Local(Some(sender.ref)), None)))
|
||||
alice2bob.expectNoMsg(200 millis)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,17 +2,20 @@ package fr.acinq.eclair.channel.states.g
|
|||
|
||||
import akka.actor.Status.Failure
|
||||
import akka.testkit.{TestFSMRef, TestProbe}
|
||||
import fr.acinq.bitcoin.Satoshi
|
||||
import fr.acinq.eclair.blockchain._
|
||||
import fr.acinq.eclair.blockchain.fee.FeeratesPerKw
|
||||
import fr.acinq.eclair.channel.Helpers.Closing
|
||||
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
||||
import fr.acinq.eclair.channel.{Data, State, _}
|
||||
import fr.acinq.eclair.wire.{ClosingSigned, Error, Shutdown}
|
||||
import fr.acinq.eclair.wire.{ClosingSigned, CommitSig, Error, Shutdown}
|
||||
import fr.acinq.eclair.{Globals, TestkitBaseClass}
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.Tag
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.Success
|
||||
|
||||
/**
|
||||
* Created by PM on 05/07/2016.
|
||||
|
@ -37,7 +40,7 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods
|
|||
// NB: at this point, bob has already computed and sent the first ClosingSigned message
|
||||
// In order to force a fee negotiation, we will change the current fee before forwarding
|
||||
// the Shutdown message to alice, so that alice computes a different initial closing fee.
|
||||
if (test.tags.contains("fee2")) Globals.feeratesPerKw.set(FeeratesPerKw.single(4316)) else Globals.feeratesPerKw.set(FeeratesPerKw.single(20000))
|
||||
if (test.tags.contains("fee2")) Globals.feeratesPerKw.set(FeeratesPerKw.single(4316)) else Globals.feeratesPerKw.set(FeeratesPerKw.single(5000))
|
||||
bob2alice.forward(alice)
|
||||
awaitCond(alice.stateName == NEGOTIATING)
|
||||
awaitCond(bob.stateName == NEGOTIATING)
|
||||
|
@ -45,18 +48,18 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods
|
|||
}
|
||||
}
|
||||
|
||||
test("recv ClosingSigned (theirCloseFee != ourCloseFee)") { case (alice, bob, alice2bob, bob2alice, _, _) =>
|
||||
test("recv ClosingSigned (theirCloseFee != ourCloseFee)") { case (alice, _, alice2bob, bob2alice, _, _) =>
|
||||
within(30 seconds) {
|
||||
val aliceCloseSig1 = alice2bob.expectMsgType[ClosingSigned]
|
||||
val bobCloseSig = bob2alice.expectMsgType[ClosingSigned]
|
||||
assert(aliceCloseSig1.feeSatoshis == 2 * bobCloseSig.feeSatoshis)
|
||||
assert(bobCloseSig.feeSatoshis == 2 * aliceCloseSig1.feeSatoshis)
|
||||
// actual test starts here
|
||||
val initialState = alice.stateData.asInstanceOf[DATA_NEGOTIATING]
|
||||
bob2alice.forward(alice)
|
||||
val aliceCloseSig2 = alice2bob.expectMsgType[ClosingSigned]
|
||||
// BOLT 2: If the receiver [doesn't agree with the fee] it SHOULD propose a value strictly between the received fee-satoshis and its previously-sent fee-satoshis
|
||||
assert(aliceCloseSig2.feeSatoshis < aliceCloseSig1.feeSatoshis && aliceCloseSig2.feeSatoshis > bobCloseSig.feeSatoshis)
|
||||
awaitCond(alice.stateData.asInstanceOf[DATA_NEGOTIATING] == initialState.copy(localClosingSigned = aliceCloseSig2))
|
||||
assert(aliceCloseSig2.feeSatoshis > aliceCloseSig1.feeSatoshis && aliceCloseSig2.feeSatoshis < bobCloseSig.feeSatoshis)
|
||||
awaitCond(alice.stateData.asInstanceOf[DATA_NEGOTIATING] == initialState.copy(localClosingSigned = initialState.localClosingSigned :+ aliceCloseSig2))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,6 +95,34 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods
|
|||
testFeeConverge(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
||||
}
|
||||
|
||||
test("recv ClosingSigned (fee too high)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) =>
|
||||
within(30 seconds) {
|
||||
val closingSigned = bob2alice.expectMsgType[ClosingSigned]
|
||||
val sender = TestProbe()
|
||||
val tx = bob.stateData.asInstanceOf[DATA_NEGOTIATING].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
sender.send(bob, closingSigned.copy(feeSatoshis = 99000)) // sig doesn't matter, it is checked later
|
||||
val error = bob2alice.expectMsgType[Error]
|
||||
assert(new String(error.data).startsWith("invalid close fee: fee_satoshis=99000"))
|
||||
bob2blockchain.expectMsg(PublishAsap(tx))
|
||||
bob2blockchain.expectMsgType[PublishAsap]
|
||||
bob2blockchain.expectMsgType[WatchConfirmed]
|
||||
}
|
||||
}
|
||||
|
||||
test("recv ClosingSigned (invalid sig)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) =>
|
||||
within(30 seconds) {
|
||||
val closingSigned = bob2alice.expectMsgType[ClosingSigned]
|
||||
val sender = TestProbe()
|
||||
val tx = bob.stateData.asInstanceOf[DATA_NEGOTIATING].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
sender.send(bob, closingSigned.copy(signature = "00" * 64))
|
||||
val error = bob2alice.expectMsgType[Error]
|
||||
assert(new String(error.data).startsWith("invalid close signature"))
|
||||
bob2blockchain.expectMsg(PublishAsap(tx))
|
||||
bob2blockchain.expectMsgType[PublishAsap]
|
||||
bob2blockchain.expectMsgType[WatchConfirmed]
|
||||
}
|
||||
}
|
||||
|
||||
test("recv BITCOIN_FUNDING_SPENT (counterparty's mutual close)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) =>
|
||||
within(30 seconds) {
|
||||
var aliceCloseFee, bobCloseFee = 0L
|
||||
|
@ -111,10 +142,30 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods
|
|||
assert(bob2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(mutualCloseTx))
|
||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, mutualCloseTx)
|
||||
alice2blockchain.expectNoMsg(1 second)
|
||||
assert(alice.stateName == NEGOTIATING)
|
||||
assert(alice.stateName == CLOSING)
|
||||
}
|
||||
}
|
||||
|
||||
test("recv BITCOIN_FUNDING_SPENT (an older mutual close)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) =>
|
||||
within(30 seconds) {
|
||||
val aliceClose1 = alice2bob.expectMsgType[ClosingSigned]
|
||||
alice2bob.forward(bob)
|
||||
val bobClose1 = bob2alice.expectMsgType[ClosingSigned]
|
||||
bob2alice.forward(alice)
|
||||
val aliceClose2 = alice2bob.expectMsgType[ClosingSigned]
|
||||
assert(aliceClose2.feeSatoshis != bobClose1.feeSatoshis)
|
||||
// at this point alice and bob have not yet converged on closing fees, but bob decides to publish a mutual close with one of the previous sigs
|
||||
val d = bob.stateData.asInstanceOf[DATA_NEGOTIATING]
|
||||
implicit val log = bob.underlyingActor.implicitLog
|
||||
val Success(bobClosingTx) = Closing.checkClosingSignature(d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, Satoshi(aliceClose1.feeSatoshis), aliceClose1.signature)
|
||||
|
||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobClosingTx)
|
||||
alice2blockchain.expectNoMsg(1 second)
|
||||
assert(alice.stateName == CLOSING)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
test("recv CMD_CLOSE") { case (alice, _, _, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
|
|
|
@ -28,7 +28,8 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
import setup._
|
||||
within(30 seconds) {
|
||||
reachNormal(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
||||
|
||||
relayer.expectMsgType[ChannelUpdate]
|
||||
relayer.expectMsgType[ChannelUpdate]
|
||||
val bobCommitTxes: List[Transaction] = (for (amt <- List(100000000, 200000000, 300000000)) yield {
|
||||
val (r, htlc) = addHtlc(amt, alice, bob, alice2bob, bob2alice)
|
||||
crossSign(alice, bob, alice2bob, bob2alice)
|
||||
|
@ -104,7 +105,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
test("recv BITCOIN_TX_CONFIRMED (mutual close)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, _) =>
|
||||
within(30 seconds) {
|
||||
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
||||
val mutualCloseTx = alice.stateData.asInstanceOf[DATA_CLOSING].mutualClosePublished.get
|
||||
val mutualCloseTx = alice.stateData.asInstanceOf[DATA_CLOSING].mutualClosePublished.last
|
||||
|
||||
// actual test starts here
|
||||
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(mutualCloseTx), 0, 0)
|
||||
|
@ -112,7 +113,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv BITCOIN_FUNDING_SPENT (our commit)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, _) =>
|
||||
test("recv BITCOIN_FUNDING_SPENT (our commit)") { case (alice, _, _, _, alice2blockchain, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
// an error occurs and alice publishes her commit tx
|
||||
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
|
@ -264,7 +265,6 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
test("recv BITCOIN_TX_CONFIRMED (one revoked tx)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, bobCommitTxes) =>
|
||||
within(30 seconds) {
|
||||
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
||||
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
|
||||
// bob publishes one of his revoked txes
|
||||
val bobRevokedTx = bobCommitTxes.head
|
||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobRevokedTx)
|
||||
|
@ -284,6 +284,17 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv ChannelReestablish") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, _) =>
|
||||
within(30 seconds) {
|
||||
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
||||
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
|
||||
val sender = TestProbe()
|
||||
sender.send(alice, ChannelReestablish(channelId(bob), 42, 42))
|
||||
val error = alice2bob.expectMsgType[Error]
|
||||
assert(new String(error.data) === FundingTxSpent(channelId(alice), initialState.spendingTxes.head).getMessage)
|
||||
}
|
||||
}
|
||||
|
||||
test("recv CMD_CLOSE") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, _) =>
|
||||
within(30 seconds) {
|
||||
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
||||
|
|
|
@ -6,9 +6,10 @@ import fr.acinq.eclair.channel.Helpers.Funding
|
|||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.crypto.{ShaChain, Sphinx}
|
||||
import fr.acinq.eclair.payment.{Local, Relayed}
|
||||
import fr.acinq.eclair.router.Announcements
|
||||
import fr.acinq.eclair.transactions.Transactions.CommitTx
|
||||
import fr.acinq.eclair.transactions._
|
||||
import fr.acinq.eclair.wire.{ChannelCodecs, UpdateAddHtlc}
|
||||
import fr.acinq.eclair.wire.{ChannelCodecs, ChannelUpdate, UpdateAddHtlc}
|
||||
import fr.acinq.eclair.{UInt64, randomKey}
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.FunSuite
|
||||
|
@ -96,5 +97,7 @@ object ChannelStateSpec {
|
|||
remoteNextCommitInfo = Right(randomKey.publicKey),
|
||||
commitInput = commitmentInput, remotePerCommitmentSecrets = ShaChain.init, channelId = "00" * 32)
|
||||
|
||||
val normal = DATA_NORMAL(commitments, Some(42), None, None, None)
|
||||
val channelUpdate = Announcements.makeChannelUpdate("11" * 32, randomKey, randomKey.publicKey, 142553, 42, 15, 575, 53)
|
||||
|
||||
val normal = DATA_NORMAL(commitments, 42, None, channelUpdate, None, None, None)
|
||||
}
|
||||
|
|
|
@ -7,8 +7,9 @@ import fr.acinq.bitcoin.{BinaryData, Crypto, MilliSatoshi}
|
|||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.crypto.Sphinx
|
||||
import fr.acinq.eclair.payment.PaymentLifecycle.buildCommand
|
||||
import fr.acinq.eclair.router.Announcements
|
||||
import fr.acinq.eclair.wire._
|
||||
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
|
||||
import fr.acinq.eclair.{Globals, TestConstants, TestkitBaseClass}
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
|
@ -64,7 +65,7 @@ class RelayerSpec extends TestkitBaseClass {
|
|||
paymentHandler.expectNoMsg(500 millis)
|
||||
}
|
||||
|
||||
test("fail to relay an htlc-add when there is no available upstream channel") { case (relayer, register, paymentHandler) =>
|
||||
test("fail to relay an htlc-add when we have no channel_update for the next channel") { case (relayer, register, paymentHandler) =>
|
||||
val sender = TestProbe()
|
||||
|
||||
// we use this to build a valid onion
|
||||
|
@ -76,15 +77,33 @@ class RelayerSpec extends TestkitBaseClass {
|
|||
|
||||
val fail = sender.expectMsgType[CMD_FAIL_HTLC]
|
||||
assert(fail.id === add_ab.id)
|
||||
assert(fail.reason == Right(UnknownNextPeer))
|
||||
|
||||
register.expectNoMsg(500 millis)
|
||||
paymentHandler.expectNoMsg(500 millis)
|
||||
}
|
||||
|
||||
test("fail to relay an htlc-add when the requested channel is disabled") { case (relayer, register, paymentHandler) =>
|
||||
val sender = TestProbe()
|
||||
|
||||
// we use this to build a valid onion
|
||||
val (cmd, _) = buildCommand(finalAmountMsat, finalExpiry, paymentHash, hops)
|
||||
// and then manually build an htlc
|
||||
val add_ab = UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.paymentHash, cmd.expiry, cmd.onion)
|
||||
val channelUpdate_bc_disabled = channelUpdate_bc.copy(flags = Announcements.makeFlags(Announcements.isNode1(channelUpdate_bc.flags), enable = false))
|
||||
relayer ! channelUpdate_bc_disabled
|
||||
|
||||
sender.send(relayer, ForwardAdd(add_ab))
|
||||
|
||||
val fail = sender.expectMsgType[CMD_FAIL_HTLC]
|
||||
assert(fail.id === add_ab.id)
|
||||
assert(fail.reason == Right(ChannelDisabled(channelUpdate_bc_disabled.flags, channelUpdate_bc_disabled)))
|
||||
|
||||
register.expectNoMsg(500 millis)
|
||||
paymentHandler.expectNoMsg(500 millis)
|
||||
}
|
||||
|
||||
test("fail to relay an htlc-add when the onion is malformed") { case (relayer, register, paymentHandler) =>
|
||||
|
||||
// TODO: we should use the new update_fail_malformed_htlc message (see BOLT 2)
|
||||
val sender = TestProbe()
|
||||
|
||||
// we use this to build a valid onion
|
||||
|
@ -107,7 +126,7 @@ class RelayerSpec extends TestkitBaseClass {
|
|||
val sender = TestProbe()
|
||||
|
||||
// we use this to build a valid onion
|
||||
val (cmd, secrets) = buildCommand(channelUpdate_bc.htlcMinimumMsat - 1, finalExpiry, paymentHash, hops.map(hop => hop.copy(lastUpdate = hop.lastUpdate.copy(feeBaseMsat = 0, feeProportionalMillionths = 0))))
|
||||
val (cmd, _) = buildCommand(channelUpdate_bc.htlcMinimumMsat - 1, finalExpiry, paymentHash, hops.map(hop => hop.copy(lastUpdate = hop.lastUpdate.copy(feeBaseMsat = 0, feeProportionalMillionths = 0))))
|
||||
// and then manually build an htlc
|
||||
val add_ab = UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.paymentHash, cmd.expiry, cmd.onion)
|
||||
relayer ! channelUpdate_bc
|
||||
|
@ -126,7 +145,7 @@ class RelayerSpec extends TestkitBaseClass {
|
|||
val sender = TestProbe()
|
||||
|
||||
val hops1 = hops.updated(1, hops(1).copy(lastUpdate = hops(1).lastUpdate.copy(cltvExpiryDelta = 0)))
|
||||
val (cmd, secrets) = buildCommand(finalAmountMsat, finalExpiry, paymentHash, hops1)
|
||||
val (cmd, _) = buildCommand(finalAmountMsat, finalExpiry, paymentHash, hops1)
|
||||
// and then manually build an htlc
|
||||
val add_ab = UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.paymentHash, cmd.expiry, cmd.onion)
|
||||
relayer ! channelUpdate_bc
|
||||
|
@ -144,7 +163,7 @@ class RelayerSpec extends TestkitBaseClass {
|
|||
test("fail to relay an htlc-add when expiry is too soon") { case (relayer, register, paymentHandler) =>
|
||||
val sender = TestProbe()
|
||||
|
||||
val (cmd, secrets) = buildCommand(finalAmountMsat, 0, paymentHash, hops)
|
||||
val (cmd, _) = buildCommand(finalAmountMsat, 0, paymentHash, hops)
|
||||
// and then manually build an htlc
|
||||
val add_ab = UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.paymentHash, cmd.expiry, cmd.onion)
|
||||
relayer ! channelUpdate_bc
|
||||
|
@ -164,7 +183,7 @@ class RelayerSpec extends TestkitBaseClass {
|
|||
|
||||
// to simulate this we use a zero-hop route A->B where A is the 'attacker'
|
||||
val hops1 = hops.head :: Nil
|
||||
val (cmd, secrets) = buildCommand(finalAmountMsat, finalExpiry, paymentHash, hops1)
|
||||
val (cmd, _) = buildCommand(finalAmountMsat, finalExpiry, paymentHash, hops1)
|
||||
// and then manually build an htlc with a wrong expiry
|
||||
val add_ab = UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat - 1, cmd.paymentHash, cmd.expiry, cmd.onion)
|
||||
relayer ! channelUpdate_bc
|
||||
|
@ -184,7 +203,7 @@ class RelayerSpec extends TestkitBaseClass {
|
|||
|
||||
// to simulate this we use a zero-hop route A->B where A is the 'attacker'
|
||||
val hops1 = hops.head :: Nil
|
||||
val (cmd, secrets) = buildCommand(finalAmountMsat, finalExpiry, paymentHash, hops1)
|
||||
val (cmd, _) = buildCommand(finalAmountMsat, finalExpiry, paymentHash, hops1)
|
||||
// and then manually build an htlc with a wrong expiry
|
||||
val add_ab = UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.paymentHash, cmd.expiry - 1, cmd.onion)
|
||||
relayer ! channelUpdate_bc
|
||||
|
@ -199,6 +218,78 @@ class RelayerSpec extends TestkitBaseClass {
|
|||
paymentHandler.expectNoMsg(500 millis)
|
||||
}
|
||||
|
||||
test("fail to relay an htlc-add when next channel's balance is too low") { case (relayer, register, paymentHandler) =>
|
||||
val sender = TestProbe()
|
||||
|
||||
val (cmd, _) = buildCommand(finalAmountMsat, Globals.blockCount.get().toInt + 10, paymentHash, hops)
|
||||
// and then manually build an htlc
|
||||
val add_ab = UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.paymentHash, cmd.expiry, cmd.onion)
|
||||
relayer ! channelUpdate_bc
|
||||
|
||||
sender.send(relayer, ForwardAdd(add_ab))
|
||||
|
||||
val fwd = register.expectMsgType[Register.ForwardShortId[CMD_ADD_HTLC]]
|
||||
assert(fwd.shortChannelId === channelUpdate_bc.shortChannelId)
|
||||
assert(fwd.message.upstream_opt === Some(add_ab))
|
||||
|
||||
sender.send(relayer, AddHtlcFailed(channelId_bc, new InsufficientFunds(channelId_bc, cmd.amountMsat, 100, 0, 0), Relayed(add_ab.channelId, add_ab.id, add_ab.amountMsat, cmd.amountMsat), Some(channelUpdate_bc)))
|
||||
|
||||
val fail = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
||||
assert(fail.id === add_ab.id)
|
||||
assert(fail.reason == Right(TemporaryChannelFailure(channelUpdate_bc)))
|
||||
|
||||
register.expectNoMsg(500 millis)
|
||||
paymentHandler.expectNoMsg(500 millis)
|
||||
}
|
||||
|
||||
test("fail to relay an htlc-add when next channel has too many inflight htlcs") { case (relayer, register, paymentHandler) =>
|
||||
val sender = TestProbe()
|
||||
|
||||
val (cmd, _) = buildCommand(finalAmountMsat, Globals.blockCount.get().toInt + 10, paymentHash, hops)
|
||||
// and then manually build an htlc
|
||||
val add_ab = UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.paymentHash, cmd.expiry, cmd.onion)
|
||||
relayer ! channelUpdate_bc
|
||||
|
||||
sender.send(relayer, ForwardAdd(add_ab))
|
||||
|
||||
val fwd = register.expectMsgType[Register.ForwardShortId[CMD_ADD_HTLC]]
|
||||
assert(fwd.shortChannelId === channelUpdate_bc.shortChannelId)
|
||||
assert(fwd.message.upstream_opt === Some(add_ab))
|
||||
|
||||
sender.send(relayer, AddHtlcFailed(channelId_bc, new TooManyAcceptedHtlcs(channelId_bc, 30), Relayed(add_ab.channelId, add_ab.id, add_ab.amountMsat, cmd.amountMsat), Some(channelUpdate_bc)))
|
||||
|
||||
val fail = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
||||
assert(fail.id === add_ab.id)
|
||||
assert(fail.reason == Right(TemporaryChannelFailure(channelUpdate_bc)))
|
||||
|
||||
register.expectNoMsg(500 millis)
|
||||
paymentHandler.expectNoMsg(500 millis)
|
||||
}
|
||||
|
||||
test("fail to relay an htlc-add when next channel has a timed out htlc (and is thus closing)") { case (relayer, register, paymentHandler) =>
|
||||
val sender = TestProbe()
|
||||
|
||||
val (cmd, _) = buildCommand(finalAmountMsat, Globals.blockCount.get().toInt + 10, paymentHash, hops)
|
||||
// and then manually build an htlc
|
||||
val add_ab = UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.paymentHash, cmd.expiry, cmd.onion)
|
||||
relayer ! channelUpdate_bc
|
||||
|
||||
sender.send(relayer, ForwardAdd(add_ab))
|
||||
|
||||
val fwd = register.expectMsgType[Register.ForwardShortId[CMD_ADD_HTLC]]
|
||||
assert(fwd.shortChannelId === channelUpdate_bc.shortChannelId)
|
||||
assert(fwd.message.upstream_opt === Some(add_ab))
|
||||
|
||||
sender.send(relayer, AddHtlcFailed(channelId_bc, new HtlcTimedout(channelId_bc), Relayed(add_ab.channelId, add_ab.id, add_ab.amountMsat, cmd.amountMsat), Some(channelUpdate_bc)))
|
||||
|
||||
val fail = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
||||
assert(fail.id === add_ab.id)
|
||||
assert(fail.reason == Right(PermanentChannelFailure))
|
||||
|
||||
register.expectNoMsg(500 millis)
|
||||
paymentHandler.expectNoMsg(500 millis)
|
||||
}
|
||||
|
||||
test("relay an htlc-fulfill") { case (relayer, register, _) =>
|
||||
val sender = TestProbe()
|
||||
val eventListener = TestProbe()
|
||||
|
|
|
@ -198,7 +198,6 @@ class LightningMessageCodecsSpec extends FunSuite {
|
|||
|
||||
msgs.foreach {
|
||||
case msg => {
|
||||
println(msg)
|
||||
val encoded = lightningMessageCodec.encode(msg).require
|
||||
val decoded = lightningMessageCodec.decode(encoded).require
|
||||
assert(msg === decoded.value)
|
||||
|
|
|
@ -15,17 +15,6 @@
|
|||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>pl.project13.maven</groupId>
|
||||
<artifactId>git-commit-id-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>revision</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
|
|
17
eclair-node/src/main/resources/application.conf
Normal file
17
eclair-node/src/main/resources/application.conf
Normal file
|
@ -0,0 +1,17 @@
|
|||
akka {
|
||||
loggers = ["akka.event.slf4j.Slf4jLogger"]
|
||||
loglevel = "DEBUG"
|
||||
|
||||
actor {
|
||||
debug {
|
||||
# enable DEBUG logging of all LoggingFSMs for events, transitions and timers
|
||||
fsm = on
|
||||
}
|
||||
}
|
||||
|
||||
http {
|
||||
host-connection-pool {
|
||||
max-open-requests = 64
|
||||
}
|
||||
}
|
||||
}
|
43
pom.xml
43
pom.xml
|
@ -46,6 +46,9 @@
|
|||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>1.7</maven.compiler.source>
|
||||
<maven.compiler.target>1.7</maven.compiler.target>
|
||||
<!-- default tag used when building without git -->
|
||||
<git.commit.id>notag</git.commit.id>
|
||||
<git.commit.id.abbrev>notag</git.commit.id.abbrev>
|
||||
<scala.version>2.11.11</scala.version>
|
||||
<scala.version.short>2.11</scala.version.short>
|
||||
<akka.version>2.3.14</akka.version>
|
||||
|
@ -56,6 +59,11 @@
|
|||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>3.0.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
|
@ -64,7 +72,7 @@
|
|||
<plugin>
|
||||
<groupId>pl.project13.maven</groupId>
|
||||
<artifactId>git-commit-id-plugin</artifactId>
|
||||
<version>2.2.2</version>
|
||||
<version>2.2.3</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.github.chrisdchristo</groupId>
|
||||
|
@ -79,38 +87,53 @@
|
|||
<artifactId>versions-maven-plugin</artifactId>
|
||||
<version>2.3</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>pl.project13.maven</groupId>
|
||||
<artifactId>git-commit-id-plugin</artifactId>
|
||||
<version>2.2.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>revision</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<failOnNoGitDirectory>false</failOnNoGitDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>net.alchim31.maven</groupId>
|
||||
<artifactId>scala-maven-plugin</artifactId>
|
||||
<version>3.3.1</version>
|
||||
<configuration>
|
||||
<args>
|
||||
<args combine.children="append">
|
||||
<arg>-deprecation</arg>
|
||||
<arg>-feature</arg>
|
||||
<arg>-language:postfixOps</arg>
|
||||
<arg>-language:implicitConversions</arg>
|
||||
<arg>-Xfatal-warnings</arg>
|
||||
</args>
|
||||
<scalaCompatVersion>${scala.version.short}</scalaCompatVersion>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>process-resources</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>scalac</id>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
<goal>testCompile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<id>scaladoc</id>
|
||||
<goals>
|
||||
<goal>doc-jar</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<args>
|
||||
<arg>-no-link-warnings</arg>
|
||||
</args>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
|
Loading…
Add table
Reference in a new issue