1
0
mirror of https://github.com/ACINQ/eclair.git synced 2024-11-20 10:39:19 +01:00

channels are now multiplexed over single connections between nodes

This commit is contained in:
pm47 2017-02-15 18:08:44 +01:00
parent e2fc3fbecc
commit 7d79f65bf2
33 changed files with 618 additions and 391 deletions

View File

@ -18,6 +18,15 @@
</encoder>
</appender>
<appender name="RED" class="ch.qos.logback.core.ConsoleAppender">
<target>System.out</target>
<withJansi>false</withJansi>
<encoder>
<pattern>%yellow(${HOSTNAME} %d) %highlight(%-5level) %logger{36} %X{akkaSource} - %red(%msg) %ex{12}%n
</pattern>
</encoder>
</appender>
<appender name="BLUE" class="ch.qos.logback.core.ConsoleAppender">
<target>System.out</target>
<withJansi>false</withJansi>
@ -45,6 +54,10 @@
</encoder>
</appender>
<logger name="fr.acinq.eclair.io.Peer" level="DEBUG" additivity="false">
<appender-ref ref="RED"/>
</logger>
<logger name="fr.acinq.eclair.channel" level="DEBUG" additivity="false">
<appender-ref ref="BLUE"/>
</logger>

View File

@ -8,14 +8,13 @@ import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import akka.util.Timeout
import com.typesafe.config.ConfigFactory
import fr.acinq.bitcoin.{Base58Check, BinaryData, MilliSatoshi, OP_CHECKSIG, OP_DUP, OP_EQUALVERIFY, OP_HASH160, OP_PUSHDATA, Satoshi}
import fr.acinq.bitcoin.{Base58Check, OP_CHECKSIG, OP_DUP, OP_EQUALVERIFY, OP_HASH160, OP_PUSHDATA}
import fr.acinq.eclair.api.Service
import fr.acinq.eclair.blockchain.peer.PeerClient
import fr.acinq.eclair.blockchain.rpc.BitcoinJsonRPCClient
import fr.acinq.eclair.blockchain.{ExtendedBitcoinClient, PeerWatcher}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.gui.FxApp
import fr.acinq.eclair.io.{Client, Server}
import fr.acinq.eclair.io.{Server, Switchboard}
import fr.acinq.eclair.payment._
import fr.acinq.eclair.router._
import grizzled.slf4j.Logging
@ -91,17 +90,15 @@ class Setup() extends Logging {
val relayer = system.actorOf(Relayer.props(Globals.Node.privateKey, paymentHandler), name = "relayer")
val router = system.actorOf(Router.props(watcher), name = "router")
val paymentInitiator = system.actorOf(PaymentInitiator.props(Globals.Node.publicKey, router), "payment-initiator")
val register = system.actorOf(Register.props(watcher, router, relayer, finalScriptPubKey), name = "register")
val server = system.actorOf(Server.props(config.getString("eclair.server.host"), config.getInt("eclair.server.port"), register), "server")
val switchboard = system.actorOf(Switchboard.props(watcher, router, relayer, finalScriptPubKey), name = "switchboard")
val server = system.actorOf(Server.props(switchboard, new InetSocketAddress(config.getString("eclair.server.host"), config.getInt("eclair.server.port"))), "server")
val _setup = this
val api = new Service {
override val register: ActorRef = _setup.register
override val switchboard: ActorRef = _setup.switchboard
override val router: ActorRef = _setup.router
override val paymentHandler: ActorRef = _setup.paymentHandler
override val paymentInitiator: ActorRef = _setup.paymentInitiator
override def connect(host: String, port: Int, pubkey: BinaryData, fundingSatoshis: Satoshi, pushMsat: MilliSatoshi): Unit = system.actorOf(Client.props(host, port, pubkey, fundingSatoshis, pushMsat, register))
}
Http().bindAndHandle(api.route, config.getString("eclair.api.host"), config.getInt("eclair.api.port")) onFailure {
case t: Throwable => system.eventStream.publish(HTTPBindError)

View File

@ -1,5 +1,7 @@
package fr.acinq.eclair.api
import java.net.InetSocketAddress
import akka.actor.ActorRef
import akka.http.scaladsl.model.HttpMethods._
import akka.http.scaladsl.model.StatusCodes
@ -11,14 +13,15 @@ import akka.pattern.ask
import akka.util.Timeout
import de.heikoseeberger.akkahttpjson4s.Json4sSupport
import de.heikoseeberger.akkahttpjson4s.Json4sSupport.ShouldWritePretty
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin.{BinaryData, MilliSatoshi, Satoshi}
import fr.acinq.eclair._
import fr.acinq.eclair.channel.Register.{ListChannels, SendCommand}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.io.Switchboard.{NewChannel, NewConnection}
import fr.acinq.eclair.payment.CreatePayment
import fr.acinq.eclair.router.ChannelDesc
import grizzled.slf4j.Logging
import org.json4s.JsonAST.{JDouble, JInt, JString}
import org.json4s.JsonAST.{JInt, JString}
import org.json4s.{JValue, jackson}
import scala.concurrent.duration._
@ -47,9 +50,7 @@ trait Service extends Logging {
import Json4sSupport.{json4sMarshaller, json4sUnmarshaller}
def connect(host: String, port: Int, pubkey: BinaryData, fundingSatoshis: Satoshi, pushMsat: MilliSatoshi): Unit
def register: ActorRef
def switchboard: ActorRef
def router: ActorRef
@ -70,28 +71,31 @@ trait Service extends Logging {
entity(as[JsonRPCBody]) {
req =>
val f_res: Future[AnyRef] = req match {
case JsonRPCBody(_, _, "connect", JString(host) :: JInt(port) :: JString(pubkey) :: JInt(anchor_amount) :: Nil) =>
connect(host, port.toInt, BinaryData(pubkey), Satoshi(anchor_amount.toLong), MilliSatoshi(0))
case JsonRPCBody(_, _, "connect", JString(host) :: JInt(port) :: JString(pubkey) :: Nil) =>
switchboard ! NewConnection(PublicKey(pubkey), new InetSocketAddress(host, port.toInt), None)
Future.successful("ok")
case JsonRPCBody(_, _, "open", JString(host) :: JInt(port) :: JString(pubkey) :: JInt(funding_amount) :: Nil) =>
switchboard ! NewConnection(PublicKey(pubkey), new InetSocketAddress(host, port.toInt), Some(NewChannel(Satoshi(funding_amount.toLong), MilliSatoshi(0))))
Future.successful("ok")
case JsonRPCBody(_, _, "info", _) =>
Future.successful(Status(Globals.Node.id))
case JsonRPCBody(_, _, "list", _) =>
/*case JsonRPCBody(_, _, "list", _) =>
(register ? ListChannels).mapTo[Iterable[ActorRef]]
.flatMap(l => Future.sequence(l.map(c => c ? CMD_GETINFO)))
.flatMap(l => Future.sequence(l.map(c => c ? CMD_GETINFO)))*/
case JsonRPCBody(_, _, "network", _) =>
(router ? 'channels).mapTo[Iterable[ChannelDesc]]
case JsonRPCBody(_, _, "addhtlc", JInt(amount) :: JString(rhash) :: JString(nodeId) :: Nil) =>
(paymentInitiator ? CreatePayment(amount.toLong, BinaryData(rhash), BinaryData(nodeId))).mapTo[ChannelEvent]
case JsonRPCBody(_, _, "genh", _) =>
(paymentHandler ? 'genh).mapTo[BinaryData]
case JsonRPCBody(_, _, "sign", JInt(channel) :: Nil) =>
/*case JsonRPCBody(_, _, "sign", JInt(channel) :: Nil) =>
(register ? SendCommand(channel.toLong, CMD_SIGN)).mapTo[ActorRef].map(_ => "ok")
case JsonRPCBody(_, _, "fulfillhtlc", JString(channel) :: JDouble(id) :: JString(r) :: Nil) =>
(register ? SendCommand(channel.toLong, CMD_FULFILL_HTLC(id.toLong, BinaryData(r), commit = true))).mapTo[ActorRef].map(_ => "ok")
case JsonRPCBody(_, _, "close", JString(channel) :: JString(scriptPubKey) :: Nil) =>
(register ? SendCommand(channel.toLong, CMD_CLOSE(Some(scriptPubKey)))).mapTo[ActorRef].map(_ => "ok")
case JsonRPCBody(_, _, "close", JString(channel) :: Nil) =>
(register ? SendCommand(channel.toLong, CMD_CLOSE(None))).mapTo[ActorRef].map(_ => "ok")
(register ? SendCommand(channel.toLong, CMD_CLOSE(None))).mapTo[ActorRef].map(_ => "ok")*/
case JsonRPCBody(_, _, "help", _) =>
Future.successful(List(
"info: display basic node information",

View File

@ -9,7 +9,7 @@ import fr.acinq.eclair.blockchain.peer.CurrentBlockCount
import fr.acinq.eclair.channel.Helpers.{Closing, Funding}
import fr.acinq.eclair.crypto.{Generators, ShaChain}
import fr.acinq.eclair.payment.Binding
import fr.acinq.eclair.router.{Announcements, SendRoutingState}
import fr.acinq.eclair.router.Announcements
import fr.acinq.eclair.transactions._
import fr.acinq.eclair.wire._
@ -24,12 +24,12 @@ import scala.util.{Failure, Success, Try}
*/
object Channel {
def props(them: ActorRef, blockchain: ActorRef, router: ActorRef, relayer: ActorRef, localParams: LocalParams, theirNodeId: PublicKey, autoSignInterval: Option[FiniteDuration] = None) = Props(new Channel(them, blockchain, router, relayer, localParams, theirNodeId, autoSignInterval))
def props(remote: ActorRef, blockchain: ActorRef, router: ActorRef, relayer: ActorRef, autoSignInterval: Option[FiniteDuration] = None) = Props(new Channel(remote, blockchain, router, relayer, autoSignInterval))
}
class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, relayer: ActorRef, val localParams: LocalParams, remoteNodeId: PublicKey, autoSignInterval: Option[FiniteDuration] = None)(implicit ec: ExecutionContext = ExecutionContext.Implicits.global) extends LoggingFSM[State, Data] {
class Channel(val remote: ActorRef, val blockchain: ActorRef, router: ActorRef, relayer: ActorRef, autoSignInterval: Option[FiniteDuration] = None)(implicit ec: ExecutionContext = ExecutionContext.Implicits.global) extends LoggingFSM[State, Data] {
context.system.eventStream.publish(ChannelCreated(self, localParams, remoteNodeId))
var remoteNodeId: PublicKey = null
/*
8888888 888b 888 8888888 88888888888
@ -69,25 +69,13 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
startWith(WAIT_FOR_INIT_INTERNAL, Nothing)
when(WAIT_FOR_INIT_INTERNAL)(handleExceptions {
case Event(i@INPUT_INIT_FUNDER(fundingSatoshis, pushMsat), Nothing) if localParams.isFunder =>
them ! Init(globalFeatures = localParams.globalFeatures, localFeatures = localParams.localFeatures)
goto(WAIT_FOR_INIT) using DATA_WAIT_FOR_INIT(localParams, Left(i), autoSignInterval)
case Event(i@INPUT_INIT_FUNDEE(), Nothing) if !localParams.isFunder =>
them ! Init(globalFeatures = localParams.globalFeatures, localFeatures = localParams.localFeatures)
goto(WAIT_FOR_INIT) using DATA_WAIT_FOR_INIT(localParams, Right(i), autoSignInterval)
})
when(WAIT_FOR_INIT)(handleExceptions {
case Event(Init(remoteGlobalFeatures, remoteLocalFeatures), DATA_WAIT_FOR_INIT(localParams, Left(initFunder), autoSignInterval)) =>
val temporaryChannelId = Platform.currentTime
case Event(initFunder@INPUT_INIT_FUNDER(remoteNodeId, temporaryChannelId, fundingSatoshis, pushMsat, localParams, remoteInit), Nothing) =>
this.remoteNodeId = remoteNodeId
context.system.eventStream.publish(ChannelCreated(self, localParams, remoteNodeId))
val firstPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, 0)
if (Features.requiresInitialRoutingSync(remoteLocalFeatures)) {
router ! SendRoutingState(them)
}
them ! OpenChannel(temporaryChannelId = temporaryChannelId,
fundingSatoshis = initFunder.fundingSatoshis,
pushMsat = initFunder.pushMsat,
remote ! OpenChannel(temporaryChannelId = temporaryChannelId,
fundingSatoshis = fundingSatoshis,
pushMsat = pushMsat,
dustLimitSatoshis = localParams.dustLimitSatoshis,
maxHtlcValueInFlightMsat = localParams.maxHtlcValueInFlightMsat,
channelReserveSatoshis = localParams.channelReserveSatoshis,
@ -100,27 +88,26 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
paymentBasepoint = localParams.paymentKey.toPoint,
delayedPaymentBasepoint = localParams.delayedPaymentKey.toPoint,
firstPerCommitmentPoint = firstPerCommitmentPoint)
goto(WAIT_FOR_ACCEPT_CHANNEL) using DATA_WAIT_FOR_ACCEPT_CHANNEL(temporaryChannelId, localParams, fundingSatoshis = initFunder.fundingSatoshis, pushMsat = initFunder.pushMsat, remoteGlobalFeatures = remoteGlobalFeatures, remoteLocalFeatures = remoteLocalFeatures, autoSignInterval = autoSignInterval)
goto(WAIT_FOR_ACCEPT_CHANNEL) using DATA_WAIT_FOR_ACCEPT_CHANNEL(initFunder, autoSignInterval = autoSignInterval)
case Event(Init(remoteGlobalFeatures, remoteLocalFeatures), DATA_WAIT_FOR_INIT(localParams, Right(initFundee), autoSignInterval)) =>
if (Features.requiresInitialRoutingSync(remoteLocalFeatures)) {
router ! SendRoutingState(them)
}
goto(WAIT_FOR_OPEN_CHANNEL) using DATA_WAIT_FOR_OPEN_CHANNEL(localParams, remoteGlobalFeatures = remoteGlobalFeatures, remoteLocalFeatures = remoteLocalFeatures, autoSignInterval = autoSignInterval)
case Event(inputFundee@INPUT_INIT_FUNDEE(remoteNodeId, _, localParams, _), Nothing) if !localParams.isFunder =>
this.remoteNodeId = remoteNodeId
context.system.eventStream.publish(ChannelCreated(self, localParams, remoteNodeId))
goto(WAIT_FOR_OPEN_CHANNEL) using DATA_WAIT_FOR_OPEN_CHANNEL(inputFundee, autoSignInterval = autoSignInterval)
})
when(WAIT_FOR_OPEN_CHANNEL)(handleExceptions {
case Event(open: OpenChannel, DATA_WAIT_FOR_OPEN_CHANNEL(localParams, remoteGlobalFeatures, remoteLocalFeatures, autoSignInterval)) =>
case Event(open: OpenChannel, DATA_WAIT_FOR_OPEN_CHANNEL(INPUT_INIT_FUNDEE(_, _, localParams, remoteInit), autoSignInterval)) =>
Try(Funding.validateParams(open.channelReserveSatoshis, open.fundingSatoshis)) match {
case Failure(t) =>
log.warning(t.getMessage)
them ! Error(open.temporaryChannelId, t.getMessage.getBytes)
remote ! Error(open.temporaryChannelId, t.getMessage.getBytes)
goto(CLOSED)
case Success(_) =>
// TODO: maybe also check uniqueness of temporary channel id
val minimumDepth = Globals.mindepth_blocks
val firstPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, 0)
them ! AcceptChannel(temporaryChannelId = Platform.currentTime,
remote ! AcceptChannel(temporaryChannelId = open.temporaryChannelId,
dustLimitSatoshis = localParams.dustLimitSatoshis,
maxHtlcValueInFlightMsat = localParams.maxHtlcValueInFlightMsat,
channelReserveSatoshis = localParams.channelReserveSatoshis,
@ -145,8 +132,8 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
revocationBasepoint = open.revocationBasepoint,
paymentBasepoint = open.paymentBasepoint,
delayedPaymentBasepoint = open.delayedPaymentBasepoint,
globalFeatures = remoteGlobalFeatures,
localFeatures = remoteLocalFeatures)
globalFeatures = remoteInit.globalFeatures,
localFeatures = remoteInit.localFeatures)
log.debug(s"remote params: $remoteParams")
val params = ChannelParams(
localParams = localParams.copy(feeratePerKw = open.feeratePerKw), // funder gets to choose the first feerate
@ -165,11 +152,11 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
})
when(WAIT_FOR_ACCEPT_CHANNEL)(handleExceptions {
case Event(accept: AcceptChannel, DATA_WAIT_FOR_ACCEPT_CHANNEL(temporaryChannelId, localParams, fundingSatoshis, pushMsat, remoteGlobalFeatures, remoteLocalFeatures, autoSignInterval)) =>
case Event(accept: AcceptChannel, DATA_WAIT_FOR_ACCEPT_CHANNEL(INPUT_INIT_FUNDER(_, temporaryChannelId, fundingSatoshis, pushMsat, localParams, remoteInit), autoSignInterval)) =>
Try(Funding.validateParams(accept.channelReserveSatoshis, fundingSatoshis)) match {
case Failure(t) =>
log.warning(t.getMessage)
them ! Error(temporaryChannelId, t.getMessage.getBytes)
remote ! Error(temporaryChannelId, t.getMessage.getBytes)
goto(CLOSED)
case _ =>
// TODO: check equality of temporaryChannelId? or should be done upstream
@ -185,9 +172,8 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
revocationBasepoint = accept.revocationBasepoint,
paymentBasepoint = accept.paymentBasepoint,
delayedPaymentBasepoint = accept.delayedPaymentBasepoint,
globalFeatures = remoteGlobalFeatures,
localFeatures = remoteLocalFeatures
)
globalFeatures = remoteInit.globalFeatures,
localFeatures = remoteInit.localFeatures)
log.debug(s"remote params: $remoteParams")
val params = ChannelParams(
localParams = localParams,
@ -215,8 +201,8 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
// let's create the first commitment tx that spends the yet uncommitted funding tx
val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(params, pushMsat, fundingTx.hash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint)
val localSigOfRemoteTx = Transactions.sign(remoteCommitTx, localParams.fundingPrivKey) // signature of their initial commitment tx that pays them pushMsat
them ! FundingCreated(
val localSigOfRemoteTx = Transactions.sign(remoteCommitTx, params.localParams.fundingPrivKey) // signature of their initial commitment tx that pays remote pushMsat
remote ! FundingCreated(
temporaryChannelId = temporaryChannelId,
txid = fundingTx.hash,
outputIndex = fundingTxOutputIndex,
@ -237,18 +223,18 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(params, pushMsat, fundingTxHash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint)
// check remote signature validity
val localSigOfLocalTx = Transactions.sign(localCommitTx, localParams.fundingPrivKey)
val localSigOfLocalTx = Transactions.sign(localCommitTx, params.localParams.fundingPrivKey)
val signedLocalCommitTx = Transactions.addSigs(localCommitTx, params.localParams.fundingPrivKey.publicKey, params.remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig)
Transactions.checkSpendable(signedLocalCommitTx) match {
case Failure(cause) =>
log.error(cause, "their FundingCreated message contains an invalid signature")
them ! Error(temporaryChannelId, cause.getMessage.getBytes)
remote ! Error(temporaryChannelId, cause.getMessage.getBytes)
// we haven't anything at stake yet, we can just stop
goto(CLOSED)
case Success(_) =>
log.info(s"signing remote tx: $remoteCommitTx")
val localSigOfRemoteTx = Transactions.sign(remoteCommitTx, localParams.fundingPrivKey)
them ! FundingSigned(
val localSigOfRemoteTx = Transactions.sign(remoteCommitTx, params.localParams.fundingPrivKey)
remote ! FundingSigned(
temporaryChannelId = temporaryChannelId,
signature = localSigOfRemoteTx
)
@ -278,12 +264,12 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
when(WAIT_FOR_FUNDING_SIGNED)(handleExceptions {
case Event(FundingSigned(_, remoteSig), DATA_WAIT_FOR_FUNDING_SIGNED(temporaryChannelId, params, fundingTx, localSpec, localCommitTx, remoteCommit)) =>
// we make sure that their sig checks out and that our first commit tx is spendable
val localSigOfLocalTx = Transactions.sign(localCommitTx, localParams.fundingPrivKey)
val localSigOfLocalTx = Transactions.sign(localCommitTx, params.localParams.fundingPrivKey)
val signedLocalCommitTx = Transactions.addSigs(localCommitTx, params.localParams.fundingPrivKey.publicKey, params.remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig)
Transactions.checkSpendable(signedLocalCommitTx) match {
case Failure(cause) =>
log.error(cause, "their FundingSigned message contains an invalid signature")
them ! Error(temporaryChannelId, cause.getMessage.getBytes)
remote ! Error(temporaryChannelId, cause.getMessage.getBytes)
// we haven't published anything yet, we can just stop
goto(CLOSED)
case Success(_) =>
@ -318,15 +304,15 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
case Event(WatchEventConfirmed(BITCOIN_FUNDING_DEPTHOK, blockHeight, txIndex), d@DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL(temporaryChannelId, params, commitments, deferred)) =>
val channelId = toShortId(blockHeight, txIndex, commitments.commitInput.outPoint.index.toInt)
blockchain ! WatchLost(self, commitments.anchorId, params.minimumDepth, BITCOIN_FUNDING_LOST)
val nextPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, 1)
them ! FundingLocked(temporaryChannelId, channelId, nextPerCommitmentPoint)
val nextPerCommitmentPoint = Generators.perCommitPoint(params.localParams.shaSeed, 1)
remote ! FundingLocked(temporaryChannelId, channelId, nextPerCommitmentPoint)
deferred.map(self ! _)
// TODO: htlcIdx should not be 0 when resuming connection
goto(WAIT_FOR_FUNDING_LOCKED) using DATA_NORMAL(params, commitments.copy(channelId = channelId), None)
// TODO: not implemented, maybe should be done with a state timer and not a blockchain watch?
case Event(BITCOIN_FUNDING_TIMEOUT, _) =>
them ! Error(0, "Funding tx timed out".getBytes)
remote ! Error(0, "Funding tx timed out".getBytes)
goto(CLOSED)
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
@ -365,7 +351,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
context.system.eventStream.subscribe(self, classOf[CurrentBlockCount])
if (Features.isChannelPublic(d.params.localParams.localFeatures) && Features.isChannelPublic(d.params.remoteParams.localFeatures)) {
val (localNodeSig, localBitcoinSig) = Announcements.signChannelAnnouncement(d.channelId, Globals.Node.privateKey, remoteNodeId, d.params.localParams.fundingPrivKey, d.params.remoteParams.fundingPubKey)
them ! AnnouncementSignatures(d.channelId, localNodeSig, localBitcoinSig)
remote ! AnnouncementSignatures(d.channelId, localNodeSig, localBitcoinSig)
goto(WAIT_FOR_ANN_SIGNATURES) using d.copy(commitments = d.commitments.copy(remoteNextCommitInfo = Right(nextPerCommitmentPoint)))
} else {
log.info(s"channel ${d.channelId} won't be announced")
@ -509,7 +495,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
case Event(msg@CommitSig(_, theirSig, theirHtlcSigs), d: DATA_NORMAL) =>
Try(Commitments.receiveCommit(d.commitments, msg)) match {
case Success((commitments1, revocation)) =>
them ! revocation
remote ! revocation
// now that we have their sig, we should propagate the htlcs newly received
(commitments1.localCommit.spec.htlcs -- d.commitments.localCommit.spec.htlcs)
.filter(_.direction == IN)
@ -552,18 +538,18 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
// first if we have pending changes, we need to commit them
val commitments2 = if (Commitments.localHasChanges(commitments)) {
val (commitments1, commit) = Commitments.sendCommit(d.commitments)
them ! commit
remote ! commit
commitments1
} else commitments
val shutdown = Shutdown(d.channelId, Script.write(params.localParams.defaultFinalScriptPubKey))
them ! shutdown
remote ! shutdown
(shutdown, commitments2)
}) match {
case Success((localShutdown, commitments3))
if (commitments3.remoteNextCommitInfo.isRight && commitments3.localCommit.spec.htlcs.size == 0 && commitments3.localCommit.spec.htlcs.size == 0)
|| (commitments3.remoteNextCommitInfo.isLeft && commitments3.localCommit.spec.htlcs.size == 0 && commitments3.remoteNextCommitInfo.left.get.spec.htlcs.size == 0) =>
val closingSigned = Closing.makeFirstClosingTx(params, commitments3, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey)
them ! closingSigned
remote ! closingSigned
goto(NEGOTIATING) using DATA_NEGOTIATING(params, commitments3, localShutdown, remoteShutdown, closingSigned)
case Success((localShutdown, commitments3)) =>
goto(SHUTDOWN) using DATA_SHUTDOWN(params, commitments3, localShutdown, remoteShutdown)
@ -643,12 +629,12 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
// TODO: we might have to propagate htlcs upstream depending on the outcome of https://github.com/ElementsProject/lightning/issues/29
Try(Commitments.receiveCommit(d.commitments, msg)) match {
case Success((commitments1, revocation)) if commitments1.hasNoPendingHtlcs =>
them ! revocation
remote ! revocation
val closingSigned = Closing.makeFirstClosingTx(params, commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey)
them ! closingSigned
remote ! closingSigned
goto(NEGOTIATING) using DATA_NEGOTIATING(params, commitments1, localShutdown, remoteShutdown, closingSigned)
case Success((commitments1, revocation)) =>
them ! revocation
remote ! revocation
stay using d.copy(commitments = commitments1)
case Failure(cause) => handleLocalError(cause, d)
}
@ -659,7 +645,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
Try(Commitments.receiveRevocation(d.commitments, msg)) match {
case Success(commitments1) if commitments1.hasNoPendingHtlcs =>
val closingSigned = Closing.makeFirstClosingTx(params, commitments, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey)
them ! closingSigned
remote ! closingSigned
goto(NEGOTIATING) using DATA_NEGOTIATING(params, commitments1, localShutdown, remoteShutdown, closingSigned)
case Success(commitments1) =>
stay using d.copy(commitments = commitments1)
@ -694,7 +680,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
case Success(signedClosingTx) =>
val nextClosingFee = Closing.nextClosingFee(Satoshi(d.localClosingSigned.feeSatoshis), Satoshi(remoteClosingFee))
val (_, closingSigned) = Closing.makeClosingTx(d.params, d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nextClosingFee)
them ! closingSigned
remote ! closingSigned
if (nextClosingFee == Satoshi(remoteClosingFee)) {
publishMutualClosing(signedClosingTx)
goto(CLOSING) using DATA_CLOSING(d.commitments, ourSignature = Some(closingSigned), mutualClosePublished = Some(signedClosingTx))
@ -747,13 +733,13 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
case Event(e: Error, d: DATA_CLOSING) => stay // nothing to do, there is already a spending tx published
}
when(CLOSED, stateTimeout = 30 seconds) {
when(CLOSED, stateTimeout = 10 seconds) {
case Event(StateTimeout, _) =>
log.info("shutting down")
stop(FSM.Normal)
}
when(ERR_INFORMATION_LEAK, stateTimeout = 30 seconds) {
when(ERR_INFORMATION_LEAK, stateTimeout = 10 seconds) {
case Event(StateTimeout, _) =>
log.info("shutting down")
stop(FSM.Normal)
@ -761,10 +747,6 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
whenUnhandled {
case Event(msg: RoutingMessage, _) =>
router forward msg
stay
case Event(WatchEventLost(BITCOIN_FUNDING_LOST), _) => goto(ERR_FUNDING_LOST)
case Event(CMD_GETSTATE, _) =>
@ -778,8 +760,8 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
case Event(CMD_GETINFO, _) =>
sender ! RES_GETINFO(remoteNodeId, stateData match {
// TODO
case c: DATA_WAIT_FOR_OPEN_CHANNEL => 0L
case c: DATA_WAIT_FOR_ACCEPT_CHANNEL => c.temporaryChannelId
case c: DATA_WAIT_FOR_OPEN_CHANNEL => c.initFundee.temporaryChannelId
case c: DATA_WAIT_FOR_ACCEPT_CHANNEL => c.initFunder.temporaryChannelId
case c: DATA_WAIT_FOR_FUNDING_CREATED => c.temporaryChannelId
case c: DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL => c.temporaryChannelId
case c: DATA_NORMAL => c.commitments.channelId
@ -810,7 +792,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
*/
def handleCommandSuccess(sender: ActorRef, msg: LightningMessage, newData: Data) = {
them ! msg
remote ! msg
if (sender != self) {
sender ! "ok"
}
@ -825,7 +807,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
def handleLocalError(cause: Throwable, d: HasCommitments) = {
log.error(cause, "")
them ! Error(0, cause.getMessage.getBytes)
remote ! Error(0, cause.getMessage.getBytes)
spendLocalCurrent(d)
}
@ -925,7 +907,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
log.warning(s"txid=${
tx.txid
} was a revoked commitment, publishing the penalty tx")
them ! Error(0, "Funding tx has been spent".getBytes)
remote ! Error(0, "Funding tx has been spent".getBytes)
// TODO hardcoded mindepth + shouldn't we watch the claim tx instead?
blockchain ! WatchConfirmed(self, tx.txid, 3, BITCOIN_PENALTY_DONE)
@ -956,7 +938,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
d.commitments.anchorId
} was spent !!")
// TODO! channel id
them ! Error(0, "Funding tx has been spent".getBytes)
remote ! Error(0, "Funding tx has been spent".getBytes)
// TODO: not enough
val commitTx = d.commitments.localCommit.publishableTxs.commitTx.tx
blockchain ! PublishAsap(commitTx)

View File

@ -5,7 +5,7 @@ import fr.acinq.bitcoin.{BinaryData, ScriptElt, Transaction}
import fr.acinq.eclair.payment.{Local, Origin}
import fr.acinq.eclair.transactions.CommitmentSpec
import fr.acinq.eclair.transactions.Transactions.CommitTx
import fr.acinq.eclair.wire.{ClosingSigned, FundingLocked, Shutdown, UpdateAddHtlc}
import fr.acinq.eclair.wire.{ClosingSigned, FundingLocked, Init, Shutdown, UpdateAddHtlc}
import scala.concurrent.duration.FiniteDuration
@ -28,7 +28,6 @@ import scala.concurrent.duration.FiniteDuration
*/
sealed trait State
case object WAIT_FOR_INIT_INTERNAL extends State
case object WAIT_FOR_INIT extends State
case object WAIT_FOR_OPEN_CHANNEL extends State
case object WAIT_FOR_ACCEPT_CHANNEL extends State
case object WAIT_FOR_FUNDING_CREATED_INTERNAL extends State
@ -57,8 +56,8 @@ case object ERR_INFORMATION_LEAK extends State
8888888888 Y8P 8888888888 888 Y888 888 "Y8888P"
*/
case class INPUT_INIT_FUNDER(fundingSatoshis: Long, pushMsat: Long)
case class INPUT_INIT_FUNDEE()
case class INPUT_INIT_FUNDER(remoteNodeId: PublicKey, temporaryChannelId: Long, fundingSatoshis: Long, pushMsat: Long, localParams: LocalParams, remoteInit: Init)
case class INPUT_INIT_FUNDEE(remoteNodeId: PublicKey, temporaryChannelId: Long, localParams: LocalParams, remoteInit: Init)
case object INPUT_NO_MORE_HTLCS
// when requesting a mutual close, we wait for as much as this timeout, then unilateral close
case object INPUT_CLOSE_COMPLETE_TIMEOUT
@ -121,9 +120,8 @@ case class LocalCommitPublished(commitTx: Transaction, claimMainDelayedOutputTx:
case class RemoteCommitPublished(commitTx: Transaction, claimMainOutputTx: Option[Transaction], claimHtlcSuccessTxs: Seq[Transaction], claimHtlcTimeoutTxs: Seq[Transaction])
case class RevokedCommitPublished(commitTx: Transaction, claimMainOutputTx: Option[Transaction], mainPenaltyTx: Option[Transaction], claimHtlcTimeoutTxs: Seq[Transaction], htlcTimeoutTxs: Seq[Transaction], htlcPenaltyTxs: Seq[Transaction])
final case class DATA_WAIT_FOR_INIT(localParams: LocalParams, internalInit: Either[INPUT_INIT_FUNDER, INPUT_INIT_FUNDEE], autoSignInterval: Option[FiniteDuration]) extends Data
final case class DATA_WAIT_FOR_OPEN_CHANNEL(localParams: LocalParams, remoteGlobalFeatures: BinaryData, remoteLocalFeatures: BinaryData, autoSignInterval: Option[FiniteDuration]) extends Data
final case class DATA_WAIT_FOR_ACCEPT_CHANNEL(temporaryChannelId: Long, localParams: LocalParams, fundingSatoshis: Long, pushMsat: Long, remoteGlobalFeatures: BinaryData, remoteLocalFeatures: BinaryData, autoSignInterval: Option[FiniteDuration]) extends Data
final case class DATA_WAIT_FOR_OPEN_CHANNEL(initFundee: INPUT_INIT_FUNDEE, autoSignInterval: Option[FiniteDuration]) extends Data
final case class DATA_WAIT_FOR_ACCEPT_CHANNEL(initFunder: INPUT_INIT_FUNDER, autoSignInterval: Option[FiniteDuration]) extends Data
final case class DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId: Long, params: ChannelParams, pushMsat: Long, remoteFirstPerCommitmentPoint: Point) extends Data
final case class DATA_WAIT_FOR_FUNDING_CREATED(temporaryChannelId: Long, params: ChannelParams, pushMsat: Long, remoteFirstPerCommitmentPoint: Point) extends Data
final case class DATA_WAIT_FOR_FUNDING_SIGNED(temporaryChannelId: Long, params: ChannelParams, fundingTx: Transaction, localSpec: CommitmentSpec, localCommitTx: CommitTx, remoteCommit: RemoteCommit) extends Data

View File

@ -1,16 +1,7 @@
package fr.acinq.eclair.channel
import akka.actor.{Props, _}
import akka.util.Timeout
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.{BinaryData, DeterministicWallet, MilliSatoshi, Satoshi, ScriptElt}
import fr.acinq.eclair.Globals
import fr.acinq.eclair.crypto.Noise.KeyPair
import fr.acinq.eclair.crypto.TransportHandler
import fr.acinq.eclair.io.LightningMessageSerializer
import fr.acinq.eclair.wire.LightningMessage
import scala.concurrent.duration._
import akka.actor.{ActorContext, ActorPath, ActorSystem, Props}
import fr.acinq.bitcoin.BinaryData
/**
* Created by PM on 26/01/2016.
@ -32,62 +23,16 @@ import scala.concurrent.duration._
* ├── client (0..m, transient)
* └── api
*/
class Register(watcher: ActorRef, router: ActorRef, relayer: ActorRef, defaultFinalScriptPubKey: Seq[ScriptElt]) extends Actor with ActorLogging {
/*class Register(watcher: ActorRef, router: ActorRef, relayer: ActorRef, defaultFinalScriptPubKey: Seq[ScriptElt]) extends Actor with ActorLogging {
import Register._
def receive: Receive = main(0L)
def main(counter: Long): Receive = {
case CreateChannel(connection, pubkey, funding_opt, pushmsat_opt) =>
def generateKey(index: Long): PrivateKey = DeterministicWallet.derivePrivateKey(Globals.Node.extendedPrivateKey, index :: counter :: Nil).privateKey
val localParams = LocalParams(
dustLimitSatoshis = 542,
maxHtlcValueInFlightMsat = Long.MaxValue,
channelReserveSatoshis = 0,
htlcMinimumMsat = 0,
feeratePerKw = Globals.feeratePerKw,
toSelfDelay = Globals.delay_blocks,
maxAcceptedHtlcs = 100,
fundingPrivKey = generateKey(0),
revocationSecret = generateKey(1),
paymentKey = generateKey(2),
delayedPaymentKey = generateKey(3),
defaultFinalScriptPubKey = defaultFinalScriptPubKey,
shaSeed = Globals.Node.seed,
isFunder = funding_opt.isDefined,
globalFeatures = Globals.global_features,
localFeatures = Globals.local_features
)
def makeChannel(conn: ActorRef, publicKey: PublicKey, ctx: ActorContext): ActorRef = {
// note that we use transport's context and not register's context
val channel = ctx.actorOf(Channel.props(conn, watcher, router, relayer, localParams, publicKey, Some(Globals.autosign_interval)), "channel")
funding_opt match {
case Some(funding) => pushmsat_opt match {
case Some(pushmsat) => channel ! INPUT_INIT_FUNDER(funding.amount, pushmsat.amount)
case None => channel ! INPUT_INIT_FUNDER(funding.amount, 0)
}
case None => channel ! INPUT_INIT_FUNDEE()
}
channel
}
val transportHandler = context.actorOf(Props(
new TransportHandler[LightningMessage](
KeyPair(Globals.Node.publicKey.toBin, Globals.Node.privateKey.toBin),
pubkey,
isWriter = funding_opt.isDefined,
them = connection,
listenerFactory = makeChannel,
serializer = LightningMessageSerializer)),
name = s"transport-handler-${counter}")
connection ! akka.io.Tcp.Register(transportHandler)
context.become(main(counter + 1))
case ListChannels => sender ! context.children
case SendCommand(channelId, cmd) =>
val s = sender
implicit val timeout = Timeout(30 seconds)
@ -97,21 +42,20 @@ class Register(watcher: ActorRef, router: ActorRef, relayer: ActorRef, defaultFi
actor
})
}
}
}*/
object Register {
def props(blockchain: ActorRef, router: ActorRef, relayer: ActorRef, defaultFinalScriptPubKey: Seq[ScriptElt]) = Props(classOf[Register], blockchain, router, relayer, defaultFinalScriptPubKey)
/*def props(blockchain: ActorRef, router: ActorRef, relayer: ActorRef, defaultFinalScriptPubKey: Seq[ScriptElt]) = Props(classOf[Register], blockchain, router, relayer, defaultFinalScriptPubKey)
// @formatter:off
case class CreateChannel(connection: ActorRef, pubkey: Option[BinaryData], fundingSatoshis: Option[Satoshi], pushMsat: Option[MilliSatoshi])
case class CreateChannel(remoteNodeId: PublicKey, fundingSatoshis: Satoshi, pushMsat: MilliSatoshi)
case class ConnectionEstablished(connection: ActorRef, createChannel_opt: Option[CreateChannel])
case class HandshakeCompleted(transport: ActorRef, remoteNodeId: PublicKey)
case class ListChannels()
case class SendCommand(channelId: Long, cmd: Command)
// @formatter:on
*/
/**
* Once it reaches NORMAL state, channel creates a [[fr.acinq.eclair.channel.AliasActor]]
* which name is counterparty_id-anchor_id
@ -119,19 +63,21 @@ object Register {
def createAlias(nodeAddress: BinaryData, channelId: Long)(implicit context: ActorContext) =
context.actorOf(Props(new AliasActor(context.self)), name = s"$nodeAddress-${java.lang.Long.toUnsignedString(channelId)}")
def actorPathToNodeAddress(system: ActorSystem, nodeAddress: BinaryData): ActorPath =
/*def actorPathToNodeAddress(system: ActorSystem, nodeAddress: BinaryData): ActorPath =
system / "register" / "transport-handler-*" / "channel" / s"$nodeAddress-*"
def actorPathToNodeAddress(nodeAddress: BinaryData)(implicit context: ActorContext): ActorPath = actorPathToNodeAddress(context.system, nodeAddress)
*/
def actorPathToChannelId(system: ActorSystem, channelId: Long): ActorPath =
system / "register" / "transport-handler-*" / "channel" / s"*-${channelId}"
system / "switchboard" / "peer-*" / "*" / s"*-${channelId}"
def actorPathToChannelId(channelId: Long)(implicit context: ActorContext): ActorPath = actorPathToChannelId(context.system, channelId)
def actorPathToChannels()(implicit context: ActorContext): ActorPath =
context.system / "register" / "transport-handler-*" / "channel"
/*def actorPathToChannels()(implicit context: ActorContext): ActorPath =
context.system / "register" / "transport-handler-*" / "channel"*/
def actorPathToPeers()(implicit context: ActorContext): ActorPath =
context.system / "switchboard" / "peer-*"
def actorPathToTransportHandlers()(implicit context: ActorContext): ActorPath =
context.system / "register" / "transport-handler-*"
}

View File

@ -2,13 +2,11 @@ package fr.acinq.eclair.crypto
import java.nio.ByteOrder
import akka.actor.{Actor, ActorContext, ActorRef, LoggingFSM, Terminated}
import akka.actor.{Actor, ActorRef, LoggingFSM, OneForOneStrategy, SupervisorStrategy, Terminated}
import akka.actor.{Actor, ActorRef, FSM, LoggingFSM, Terminated}
import akka.io.Tcp.{PeerClosed, _}
import akka.util.ByteString
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin.{BinaryData, Protocol}
import fr.acinq.eclair.channel.{CMD_CLOSE, Command}
import fr.acinq.eclair.crypto.Noise._
import scala.annotation.tailrec
@ -24,26 +22,24 @@ import scala.util.{Failure, Success, Try}
* Once the initial handshake has been completed successfully, the handler will create a listener actor with the
* provided factory, and will forward it all decrypted messages
*
* @param keyPair private/public key pair for this node
* @param rs remote node static public key (which must be known before we initiate communication)
* @param them actor htat represents the other node's
* @param isWriter if true we initiate the dialog
* @param listenerFactory factory that will be used to create the listener that will receive decrypted messages once the
* handshake phase as been completed. Its parameters are a tuple (transport handler, remote public key)
* @param keyPair private/public key pair for this node
* @param rs remote node static public key (which must be known before we initiate communication)
* @param connection actor that represents the other node's
*/
class TransportHandler[T: ClassTag](keyPair: KeyPair, rs: Option[BinaryData], them: ActorRef, isWriter: Boolean, listenerFactory: (ActorRef, PublicKey, ActorContext) => ActorRef, serializer: TransportHandler.Serializer[T]) extends Actor with LoggingFSM[TransportHandler.State, TransportHandler.Data] {
class TransportHandler[T: ClassTag](keyPair: KeyPair, rs: Option[BinaryData], connection: ActorRef, serializer: TransportHandler.Serializer[T]) extends Actor with LoggingFSM[TransportHandler.State, TransportHandler.Data] {
import TransportHandler._
if (isWriter) require(rs.isDefined)
// it means we initiate the dialog
val isWriter = rs.isDefined
context.watch(them)
context.watch(connection)
val reader = if (isWriter) {
val state = makeWriter(keyPair, rs.get)
val (state1, message, None) = state.write(BinaryData.empty)
log.debug(s"sending prefix + $message")
them ! Write(TransportHandler.prefix +: message)
connection ! Write(TransportHandler.prefix +: message)
state1
} else {
makeReader(keyPair)
@ -72,11 +68,10 @@ class TransportHandler[T: ClassTag](keyPair: KeyPair, rs: Option[BinaryData], th
reader.read(payload) match {
case (writer, _, Some((dec, enc, ck))) =>
val listener = listenerFactory(self, PublicKey(writer.rs), context)
context.watch(listener)
val (nextStateData, plaintextMessages) = WaitingForCyphertextData(ExtendedCipherState(enc, ck), ExtendedCipherState(dec, ck), None, remainder, listener).decrypt
sendToListener(listener, plaintextMessages)
goto(WaitingForCyphertext) using nextStateData
val remoteNodeId = PublicKey(writer.rs)
context.parent ! HandshakeCompleted(self, remoteNodeId)
val nextStateData = WaitingForListenerData(ExtendedCipherState(enc, ck), ExtendedCipherState(dec, ck), remainder)
goto(WaitingForListener) using nextStateData
case (writer, _, None) => {
writer.write(BinaryData.empty) match {
@ -84,68 +79,61 @@ class TransportHandler[T: ClassTag](keyPair: KeyPair, rs: Option[BinaryData], th
// we're still in the middle of the handshake process and the other end must first received our next
// message before they can reply
require(remainder.isEmpty, "unexpected additional data received during handshake")
them ! Write(TransportHandler.prefix +: message)
connection ! Write(TransportHandler.prefix +: message)
stay using HandshakeData(reader1, remainder)
}
case (_, message, Some((enc, dec, ck))) => {
them ! Write(TransportHandler.prefix +: message)
val listener = listenerFactory(self, PublicKey(writer.rs), context)
context.watch(listener)
val (nextStateData, plaintextMessages) = WaitingForCyphertextData(ExtendedCipherState(enc, ck), ExtendedCipherState(dec, ck), None, remainder, listener).decrypt
sendToListener(listener, plaintextMessages)
goto(WaitingForCyphertext) using nextStateData
connection ! Write(TransportHandler.prefix +: message)
val remoteNodeId = PublicKey(writer.rs)
context.parent ! HandshakeCompleted(self, remoteNodeId)
val nextStateData = WaitingForListenerData(ExtendedCipherState(enc, ck), ExtendedCipherState(dec, ck), remainder)
goto(WaitingForListener) using nextStateData
}
}
}
}
}
}
when(WaitingForListener) {
case Event(Received(data), currentStateData@WaitingForListenerData(enc, dec, buffer)) =>
stay using currentStateData.copy(buffer = buffer ++ data)
case Event(Listener(listener), WaitingForListenerData(enc, dec, buffer)) =>
val (nextStateData, plaintextMessages) = WaitingForCyphertextData(enc, dec, None, buffer, listener).decrypt
context.watch(listener)
sendToListener(listener, plaintextMessages)
goto(WaitingForCyphertext) using nextStateData
case Event(ErrorClosed(cause), _) =>
log.warning(s"connection closed: $cause")
context.stop(self)
stay()
}
when(WaitingForCyphertext) {
case Event(Terminated(actor), WaitingForCyphertextData(_, _, _, _, listener)) if actor == listener =>
context.stop(self)
stay()
case Event(Received(data), currentStateData@WaitingForCyphertextData(enc, dec, length, buffer, listener)) =>
val (nextStateData, plaintextMessages) = WaitingForCyphertextData.decrypt(currentStateData.copy(buffer = buffer ++ data))
sendToListener(listener, plaintextMessages)
stay using nextStateData
case Event(plaintext: BinaryData, WaitingForCyphertextData(enc, dec, length, buffer, listener)) =>
val (enc1, ciphertext) = TransportHandler.encrypt(enc, plaintext)
them ! Write(ByteString.fromArray(ciphertext.toArray))
stay using WaitingForCyphertextData(enc1, dec, length, buffer, listener)
case Event(t: T, WaitingForCyphertextData(enc, dec, length, buffer, listener)) =>
val blob = serializer.serialize(t)
val (enc1, ciphertext) = TransportHandler.encrypt(enc, blob)
them ! Write(ByteString.fromArray(ciphertext.toArray))
connection ! Write(ByteString.fromArray(ciphertext.toArray))
stay using WaitingForCyphertextData(enc1, dec, length, buffer, listener)
case Event(cmd: Command, WaitingForCyphertextData(_, _, _, _, listener)) =>
listener forward cmd
stay
case Event(ErrorClosed(cause), WaitingForCyphertextData(_, _, _, _, listener)) =>
// we transform connection closed events into application error so that it triggers a uniclose
log.error(s"tcp connection error: $cause")
listener ! fr.acinq.eclair.wire.Error(0, cause.getBytes("UTF-8"))
stay
case Event(PeerClosed, WaitingForCyphertextData(_, _, _, _, listener)) =>
listener ! fr.acinq.eclair.wire.Error(0, "peer closed".getBytes("UTF-8"))
stay
}
whenUnhandled {
case Event(Terminated(actor), _) if actor == them =>
log.warning("peer closed")
stay()
case Event(ErrorClosed(cause), _) =>
// we transform connection closed events into application error so that it triggers a uniclose
log.warning(s"tcp connection error: $cause")
stop(FSM.Normal)
case Event(PeerClosed, _) =>
log.warning(s"connection closed")
stop(FSM.Normal)
case Event(Terminated(actor), _) if actor == connection =>
// this can be the connection or the listener, either way it is a cause of death
stop(FSM.Normal)
case Event(message, _) =>
log.warning(s"unhandled $message")
@ -209,11 +197,15 @@ object TransportHandler {
localStatic, KeyPair(BinaryData.empty, BinaryData.empty), BinaryData.empty, BinaryData.empty,
Noise.Secp256k1DHFunctions, Noise.Chacha20Poly1305CipherFunctions, Noise.SHA256HashFunctions)
// @formatter:off
sealed trait State
case object Handshake extends State
case object WaitingForListener extends State
case object WaitingForCyphertext extends State
// @formatter:on
case class Listener(listener: ActorRef)
case class HandshakeCompleted(transport: ActorRef, remoteNodeId: PublicKey)
sealed trait Data
@ -261,6 +253,8 @@ object TransportHandler {
}
}
case class WaitingForListenerData(enc: CipherState, dec: CipherState, buffer: ByteString) extends Data
case class WaitingForCyphertextData(enc: CipherState, dec: CipherState, ciphertextLength: Option[Int], buffer: ByteString, listener: ActorRef) extends Data {
def decrypt: (WaitingForCyphertextData, Seq[BinaryData]) = WaitingForCyphertextData.decrypt(this)
}

View File

@ -4,16 +4,19 @@ package fr.acinq.eclair.gui
import java.awt.TrayIcon.MessageType
import java.awt.{SystemTray, TrayIcon}
import java.io.{File, FileWriter}
import java.net.InetSocketAddress
import java.time.LocalDateTime
import java.time.format.{DateTimeFormatter, FormatStyle}
import javafx.application.Platform
import javafx.scene.control.TextArea
import akka.pattern.ask
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin.{BinaryData, MilliSatoshi, Satoshi}
import fr.acinq.eclair._
import fr.acinq.eclair.gui.utils.GUIValidators
import fr.acinq.eclair.io.Client
import fr.acinq.eclair.io.Switchboard.{NewChannel, NewConnection}
import fr.acinq.eclair.payment.CreatePayment
import grizzled.slf4j.Logging
@ -28,9 +31,14 @@ class Handlers(setup: Setup, trayIcon: TrayIcon) extends Logging {
def open(hostPort: String, fundingSatoshis: Satoshi, pushMsat: MilliSatoshi) = {
hostPort match {
case GUIValidators.hostRegex(pubkey, host, port) =>
logger.info(s"connecting to $host:$port")
system.actorOf(Client.props(host, port.toInt, pubkey, fundingSatoshis, pushMsat, register))
case GUIValidators.hostRegex(remoteNodeId, host, port) =>
logger.info(s"opening a channel with remoteNodeId=$remoteNodeId")
(setup.switchboard ? NewConnection(PublicKey(remoteNodeId), new InetSocketAddress(host, port.toInt), Some(NewChannel(fundingSatoshis, pushMsat)))).mapTo[String].onComplete {
case Success(s) => {}
case Failure(t) =>
val message = s"$host:$port"
notification("Connection failed", message, TrayIcon.MessageType.WARNING)
}
case _ => {}
}
}

View File

@ -2,36 +2,57 @@ package fr.acinq.eclair.io
import java.net.InetSocketAddress
import akka.actor._
import akka.actor.{Props, _}
import akka.io.{IO, Tcp}
import fr.acinq.bitcoin.{BinaryData, MilliSatoshi, Satoshi}
import fr.acinq.eclair.channel.Register.CreateChannel
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.eclair.Globals
import fr.acinq.eclair.crypto.Noise.KeyPair
import fr.acinq.eclair.crypto.TransportHandler
import fr.acinq.eclair.crypto.TransportHandler.HandshakeCompleted
import fr.acinq.eclair.wire.LightningMessage
/**
* Created by PM on 27/10/2015.
*/
class Client(remote: InetSocketAddress, pubkey: BinaryData, fundingSatoshis: Satoshi, pushMsat: MilliSatoshi, register: ActorRef) extends Actor with ActorLogging {
class Client(switchboard: ActorRef, address: InetSocketAddress, remoteNodeId: PublicKey, origin: ActorRef) extends Actor with ActorLogging {
import Tcp._
import context.system
IO(Tcp) ! Connect(remote)
IO(Tcp) ! Connect(address)
def receive = {
case CommandFailed(_: Connect) => context stop self
case CommandFailed(_: Connect) =>
origin ! Status.Failure(new RuntimeException("connection failed"))
context stop self
case c@Connected(remote, local) =>
case Connected(remote, _) =>
log.info(s"connected to $remote")
val connection = sender()
register ! CreateChannel(connection, Some(pubkey), Some(fundingSatoshis), Some(pushMsat))
// TODO: kill this actor ?
val connection = sender
val transport = context.actorOf(Props(
new TransportHandler[LightningMessage](
KeyPair(Globals.Node.publicKey.toBin, Globals.Node.privateKey.toBin),
Some(remoteNodeId),
connection = connection,
serializer = LightningMessageSerializer)))
connection ! akka.io.Tcp.Register(transport)
context watch transport
context become connected(transport)
}
def connected(transport: ActorRef): Receive = {
case Terminated(actor) if actor == transport =>
origin ! Status.Failure(new RuntimeException("authentication failed"))
context stop self
case h: HandshakeCompleted =>
origin ! "connected"
switchboard ! h
}
}
object Client extends App {
def props(address: InetSocketAddress, pubkey: BinaryData, fundingSatoshis: Satoshi, pushMsat: MilliSatoshi, register: ActorRef): Props = Props(classOf[Client], address, pubkey, fundingSatoshis, pushMsat, register)
def props(host: String, port: Int, pubkey: BinaryData, fundingSatoshis: Satoshi, pushMsat: MilliSatoshi, register: ActorRef): Props = Props(classOf[Client], new InetSocketAddress(host, port), pubkey, fundingSatoshis, pushMsat, register)
def props(switchboard: ActorRef, address: InetSocketAddress, remoteNodeId: PublicKey, origin: ActorRef): Props = Props(classOf[Client], switchboard, address, remoteNodeId, origin)
}

View File

@ -22,4 +22,4 @@ object LightningMessageSerializer extends TransportHandler.Serializer[LightningM
case Attempt.Successful(DecodeResult(msg, _)) => msg
case Attempt.Failure(cause) => throw new RuntimeException(s"deserialization error: $cause")
}
}
}

View File

@ -0,0 +1,182 @@
package fr.acinq.eclair.io
import java.io.File
import java.net.InetSocketAddress
import akka.actor.{ActorRef, LoggingFSM, PoisonPill, Props, Terminated}
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.{DeterministicWallet, ScriptElt}
import fr.acinq.eclair.channel.{Channel, INPUT_INIT_FUNDEE, INPUT_INIT_FUNDER, LocalParams}
import fr.acinq.eclair.crypto.TransportHandler.{HandshakeCompleted, Listener}
import fr.acinq.eclair.io.Switchboard.{NewChannel, NewConnection}
import fr.acinq.eclair.router.SendRoutingState
import fr.acinq.eclair.wire._
import fr.acinq.eclair.{Features, Globals}
import scala.compat.Platform
// @formatter:off
case object Reconnect
//case class ChannelIdSwitch(previousId: Long, nextId: Long)
sealed trait OfflineChannel
case class BrandNewChannel(c: NewChannel) extends OfflineChannel
case class ColdChannel(f: File) extends OfflineChannel
case class HotChannel(a: ActorRef, channelId: Long) extends OfflineChannel
sealed trait Data
case class DisconnectedData(offlineChannels: Seq[OfflineChannel]) extends Data
case class InitializingData(transport: ActorRef, offlineChannels: Seq[OfflineChannel]) extends Data
case class ConnectedData(transport: ActorRef, remoteInit: Init, channels: Map[Long, ActorRef]) extends Data
sealed trait State
case object DISCONNECTED extends State
case object INITIALIZING extends State
case object CONNECTED extends State
// @formatter:on
/**
* Created by PM on 26/08/2016.
*/
class Peer(remoteNodeId: PublicKey, address_opt: Option[InetSocketAddress], watcher: ActorRef, router: ActorRef, relayer: ActorRef, defaultFinalScriptPubKey: Seq[ScriptElt]) extends LoggingFSM[State, Data] {
import Peer._
startWith(DISCONNECTED, DisconnectedData(Nil))
when(DISCONNECTED) {
case Event(c: NewChannel, d@DisconnectedData(offlineChannels)) =>
stay using d.copy(offlineChannels = offlineChannels :+ BrandNewChannel(c))
case Event(Reconnect, _) if address_opt.isDefined =>
context.parent ! NewConnection(remoteNodeId, address_opt.get, None)
stay
case Event(HandshakeCompleted(transport, _), DisconnectedData(channels)) =>
log.info(s"registering as a listener to $transport")
transport ! Listener(self)
context watch transport
transport ! Init(globalFeatures = Globals.global_features, localFeatures = Globals.local_features)
goto(INITIALIZING) using InitializingData(transport, channels)
}
when(INITIALIZING) {
case Event(Reconnect, _) => stay
case Event(c: NewChannel, d@InitializingData(_, offlineChannels)) =>
stay using d.copy(offlineChannels = offlineChannels :+ BrandNewChannel(c))
case Event(remoteInit: Init, InitializingData(transport, channels)) =>
import fr.acinq.eclair.Features._
log.info(s"$remoteNodeId has features: channelPublic=${isChannelPublic(remoteInit.localFeatures)} initialRoutingSync=${requiresInitialRoutingSync(remoteInit.localFeatures)}")
if (Features.requiresInitialRoutingSync(remoteInit.localFeatures)) {
router ! SendRoutingState(transport)
}
// let's bring existing/requested channels online
channels.map {
case BrandNewChannel(c) => self ! c
}
goto(CONNECTED) using ConnectedData(transport, remoteInit, Map())
case Event(Terminated(actor), InitializingData(transport, channels)) if actor == transport =>
log.warning(s"lost connection to $remoteNodeId")
goto(DISCONNECTED) using DisconnectedData(channels)
}
when(CONNECTED) {
case Event(Reconnect, _) => stay
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 temporaryChannelId = Platform.currentTime
val (channel, localParams) = createChannel(transport, temporaryChannelId, funder = true)
channel ! INPUT_INIT_FUNDER(remoteNodeId, temporaryChannelId, c.fundingSatoshis.amount, c.pushMsat.amount, localParams, remoteInit)
stay using d.copy(channels = channels + (temporaryChannelId -> channel))
case Event(msg: OpenChannel, d@ConnectedData(transport, remoteInit, channels)) =>
log.info(s"accepting a new channel to $remoteNodeId")
val temporaryChannelId = msg.temporaryChannelId
val (channel, localParams) = createChannel(transport, temporaryChannelId, funder = false)
channel ! INPUT_INIT_FUNDEE(remoteNodeId, temporaryChannelId, localParams, remoteInit)
channel ! msg
stay using d.copy(channels = channels + (temporaryChannelId -> channel))
case Event(msg@FundingLocked(previousId, nextId, _), d@ConnectedData(_, _, channels)) if channels.contains(previousId) =>
log.info(s"channel id switch: previousId=$previousId nextId=$nextId")
val channel = channels(previousId)
channel forward msg
//TODO: what if nextIds are different
stay using d.copy(channels = channels - previousId + (nextId -> channel))
case Event(msg: HasTemporaryChannelId, ConnectedData(_, _, channels)) if channels.contains(msg.temporaryChannelId) =>
val channel = channels(msg.temporaryChannelId)
channel forward msg
stay
case Event(msg: HasChannelId, ConnectedData(_, _, channels)) if channels.contains(msg.channelId) =>
val channel = channels(msg.channelId)
channel forward msg
stay
case Event(msg: RoutingMessage, ConnectedData(transport, _, _)) if sender == router=>
transport forward msg
stay
case Event(msg: RoutingMessage, _) =>
router forward msg
stay
case Event(Terminated(actor), ConnectedData(transport, _, channels)) if actor == transport =>
log.warning(s"lost connection to $remoteNodeId")
channels.values.foreach(_ ! Error(0, "peer disconnected".getBytes("UTF-8")))
// TODO: no persistence yet, all channels are lost
goto(DISCONNECTED) using DisconnectedData(Nil)
case Event(Terminated(actor), d@ConnectedData(transport, _, channels)) if channels.values.toSet.contains(actor) =>
val channelId = channels.find(_._2 == actor).get._1
log.info(s"channel closed: channelId=$channelId")
if (channels.size == 1) {
log.info(s"that was the last channel open, closing the connection")
transport ! PoisonPill
}
stay using d.copy(channels = channels - channelId)
}
def createChannel(transport: ActorRef, temporaryChannelId: Long, funder: Boolean): (ActorRef, LocalParams) = {
val localParams = makeChannelParams(temporaryChannelId, defaultFinalScriptPubKey, funder)
val channel = context.actorOf(Channel.props(transport, watcher, router, relayer, Some(Globals.autosign_interval)), s"channel-$temporaryChannelId")
context watch channel
(channel, localParams)
}
}
object Peer {
def props(remoteNodeId: PublicKey, address_opt: Option[InetSocketAddress], watcher: ActorRef, router: ActorRef, relayer: ActorRef, defaultFinalScriptPubKey: Seq[ScriptElt]) = Props(classOf[Peer], remoteNodeId, address_opt, watcher, router, relayer, defaultFinalScriptPubKey)
def generateKey(keyPath: Seq[Long]): PrivateKey = DeterministicWallet.derivePrivateKey(Globals.Node.extendedPrivateKey, keyPath).privateKey
def makeChannelParams(keyIndex: Long, defaultFinalScriptPubKey: Seq[ScriptElt], isFunder: Boolean): LocalParams =
LocalParams(
dustLimitSatoshis = 542,
maxHtlcValueInFlightMsat = Long.MaxValue,
channelReserveSatoshis = 0,
htlcMinimumMsat = 0,
feeratePerKw = Globals.feeratePerKw,
toSelfDelay = Globals.delay_blocks,
maxAcceptedHtlcs = 100,
fundingPrivKey = generateKey(keyIndex :: 0L :: Nil),
revocationSecret = generateKey(keyIndex :: 1L :: Nil),
paymentKey = generateKey(keyIndex :: 2L :: Nil),
delayedPaymentKey = generateKey(keyIndex :: 3L :: Nil),
defaultFinalScriptPubKey = defaultFinalScriptPubKey,
shaSeed = Globals.Node.seed,
isFunder = isFunder,
globalFeatures = Globals.global_features,
localFeatures = Globals.local_features
)
}

View File

@ -4,39 +4,52 @@ import java.net.InetSocketAddress
import akka.actor.{Actor, ActorLogging, ActorRef, Props}
import akka.io.{IO, Tcp}
import fr.acinq.eclair.TCPBindError
import fr.acinq.eclair.channel.Register.CreateChannel
import fr.acinq.eclair.crypto.Noise.KeyPair
import fr.acinq.eclair.crypto.TransportHandler
import fr.acinq.eclair.crypto.TransportHandler.HandshakeCompleted
import fr.acinq.eclair.wire.LightningMessage
import fr.acinq.eclair.{Globals, TCPBindError}
/**
* Created by PM on 27/10/2015.
*/
class Server(address: InetSocketAddress, register: ActorRef) extends Actor with ActorLogging {
class Server(switchboard: ActorRef, address: InetSocketAddress) extends Actor with ActorLogging {
import Tcp._
import context.system
IO(Tcp) ! Bind(self, address)
def receive = {
case b@Bound(localAddress) =>
log.info(s"bound on $b")
def receive() = main(Set())
def main(transports: Set[ActorRef]): Receive = {
case Bound(localAddress) =>
log.info(s"bound on $localAddress")
case CommandFailed(_: Bind) =>
system.eventStream.publish(TCPBindError)
context stop self
case c@Connected(remote, local) =>
case Connected(remote, _) =>
log.info(s"connected to $remote")
val connection = sender()
register ! CreateChannel(connection, None, None, None)
val connection = sender
val transport = context.actorOf(Props(
new TransportHandler[LightningMessage](
KeyPair(Globals.Node.publicKey.toBin, Globals.Node.privateKey.toBin),
None,
connection = connection,
serializer = LightningMessageSerializer)))
connection ! akka.io.Tcp.Register(transport)
context become main(transports + transport)
case h: HandshakeCompleted =>
switchboard ! h
}
}
object Server extends App {
object Server {
def props(address: InetSocketAddress, register: ActorRef): Props = Props(classOf[Server], address, register)
def props(host: String, port: Int, register: ActorRef): Props = Props(classOf[Server], new InetSocketAddress(host, port), register)
def props(switchboard: ActorRef, address: InetSocketAddress): Props = Props(classOf[Server], switchboard, address)
}

View File

@ -0,0 +1,68 @@
package fr.acinq.eclair.io
import java.net.InetSocketAddress
import akka.actor.{Actor, ActorLogging, ActorRef, Props, Status, Terminated}
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin.{MilliSatoshi, Satoshi, ScriptElt}
import fr.acinq.eclair.Globals
import fr.acinq.eclair.crypto.TransportHandler.HandshakeCompleted
/**
* Ties network connections to peers.
* Created by PM on 14/02/2017.
*/
class Switchboard(watcher: ActorRef, router: ActorRef, relayer: ActorRef, defaultFinalScriptPubKey: Seq[ScriptElt]) extends Actor with ActorLogging {
import Switchboard._
def receive: Receive = main(Map(), Map())
def main(peers: Map[PublicKey, ActorRef], connections: Map[PublicKey, ActorRef]): Receive = {
case NewConnection(Globals.Node.publicKey, _, _) =>
sender ! Status.Failure(new RuntimeException("cannot open connection with oneself"))
case NewConnection(remoteNodeId, address, newChannel_opt) =>
val connection = connections.get(remoteNodeId) match {
case Some(connection) =>
log.info(s"connection to $remoteNodeId is already in progress")
connection
case None =>
log.info(s"connecting to $remoteNodeId @ $address")
val connection = context.actorOf(Client.props(self, address, remoteNodeId, sender))
context watch(connection)
connection
}
val peer = peers.get(remoteNodeId) match {
case Some(peer) => peer
case None => createPeer(remoteNodeId, Some(address))
}
newChannel_opt.map (peer forward _)
context become main(peers + (remoteNodeId -> peer), connections + (remoteNodeId -> connection))
case Terminated(actor) if connections.values.toSet.contains(actor) =>
log.info(s"$actor is dead, removing from connections")
val remoteNodeId = connections.find(_._2 == actor).get._1
context become main(peers, connections - remoteNodeId)
case h@HandshakeCompleted(_, remoteNodeId) =>
val peer = peers.getOrElse(remoteNodeId, createPeer(remoteNodeId, None))
peer forward h
context become main(peers + (remoteNodeId -> peer), connections)
}
def createPeer(remoteNodeId: PublicKey, address_opt: Option[InetSocketAddress]) = context.actorOf(Peer.props(remoteNodeId, address_opt, watcher, router, relayer, defaultFinalScriptPubKey), name = s"peer-$remoteNodeId")
}
object Switchboard {
def props(watcher: ActorRef, router: ActorRef, relayer: ActorRef, defaultFinalScriptPubKey: Seq[ScriptElt]) = Props(classOf[Switchboard], watcher, router, relayer, defaultFinalScriptPubKey)
// @formatter:off
case class NewChannel(fundingSatoshis: Satoshi, pushMsat: MilliSatoshi)
case class NewConnection(remoteNodeId: PublicKey, address: InetSocketAddress, newChannel_opt: Option[NewChannel])
// @formatter:on
}

View File

@ -95,10 +95,10 @@ object PaymentLifecycle {
val payloadsbin: Seq[BinaryData] = payloads
.map(Codecs.perHopPayloadCodec.encode(_))
.map {
case Attempt.Successful(bitVector) => BinaryData(bitVector.toByteArray)
case Attempt.Failure(cause) => throw new RuntimeException(s"serialization error: $cause")
} :+ BinaryData("00" * 20)
.map {
case Attempt.Successful(bitVector) => BinaryData(bitVector.toByteArray)
case Attempt.Failure(cause) => throw new RuntimeException(s"serialization error: $cause")
} :+ BinaryData("00" * 20)
Sphinx.makePacket(sessionKey, pubkeys, payloadsbin, associatedData)
}
@ -106,7 +106,7 @@ object PaymentLifecycle {
/**
*
* @param finalAmountMsat the final htlc amount in millisatoshis
* @param hops the hops as computed by the router
* @param hops the hops as computed by the router
* @return a (firstAmountMsat, firstExpiry, payloads) tuple where:
* - firstAmountMsat is the amount for the first htlc in the route
* - firstExpiry is the cltv expiry for the first htlc in the route

View File

@ -170,7 +170,7 @@ class Router(watcher: ActorRef) extends Actor with ActorLogging {
case 'tick_broadcast =>
log.info(s"broadcasting ${rebroadcast.size} routing messages")
rebroadcast.foreach(context.actorSelection(Register.actorPathToTransportHandlers) ! _)
rebroadcast.foreach(context.actorSelection(Register.actorPathToPeers) ! _)
context become main(nodes, channels, updates, Nil, awaiting, stash)
case 'nodes => sender ! nodes.values

View File

@ -14,8 +14,10 @@ sealed trait LightningMessage
sealed trait SetupMessage extends LightningMessage
sealed trait ChannelMessage extends LightningMessage
sealed trait HtlcMessage extends LightningMessage
sealed trait UpdateMessage extends HtlcMessage // <- not in the spec
sealed trait RoutingMessage extends LightningMessage
sealed trait HasTemporaryChannelId extends LightningMessage { def temporaryChannelId: Long } // <- not in the spec
sealed trait HasChannelId extends LightningMessage { def channelId: Long } // <- not in the spec
sealed trait UpdateMessage extends HtlcMessage // <- not in the spec
// @formatter:on
case class Init(globalFeatures: BinaryData,
@ -38,7 +40,7 @@ case class OpenChannel(temporaryChannelId: Long,
revocationBasepoint: Point,
paymentBasepoint: Point,
delayedPaymentBasepoint: Point,
firstPerCommitmentPoint: Point) extends ChannelMessage
firstPerCommitmentPoint: Point) extends ChannelMessage with HasTemporaryChannelId
case class AcceptChannel(temporaryChannelId: Long,
dustLimitSatoshis: Long,
@ -52,53 +54,53 @@ case class AcceptChannel(temporaryChannelId: Long,
revocationBasepoint: Point,
paymentBasepoint: Point,
delayedPaymentBasepoint: Point,
firstPerCommitmentPoint: Point) extends ChannelMessage
firstPerCommitmentPoint: Point) extends ChannelMessage with HasTemporaryChannelId
case class FundingCreated(temporaryChannelId: Long,
txid: BinaryData,
outputIndex: Int,
signature: BinaryData) extends ChannelMessage
signature: BinaryData) extends ChannelMessage with HasTemporaryChannelId
case class FundingSigned(temporaryChannelId: Long,
signature: BinaryData) extends ChannelMessage
signature: BinaryData) extends ChannelMessage with HasTemporaryChannelId
case class FundingLocked(temporaryChannelId: Long,
channelId: Long,
nextPerCommitmentPoint: Point) extends ChannelMessage
nextPerCommitmentPoint: Point) extends ChannelMessage with HasTemporaryChannelId
case class Shutdown(channelId: Long,
scriptPubKey: BinaryData) extends ChannelMessage
scriptPubKey: BinaryData) extends ChannelMessage with HasChannelId
case class ClosingSigned(channelId: Long,
feeSatoshis: Long,
signature: BinaryData) extends ChannelMessage
signature: BinaryData) extends ChannelMessage with HasChannelId
case class UpdateAddHtlc(channelId: Long,
id: Long,
amountMsat: Long,
expiry: Long,
paymentHash: BinaryData,
onionRoutingPacket: BinaryData) extends HtlcMessage with UpdateMessage
onionRoutingPacket: BinaryData) extends HtlcMessage with UpdateMessage with HasChannelId
case class UpdateFulfillHtlc(channelId: Long,
id: Long,
paymentPreimage: BinaryData) extends HtlcMessage with UpdateMessage
paymentPreimage: BinaryData) extends HtlcMessage with UpdateMessage with HasChannelId
case class UpdateFailHtlc(channelId: Long,
id: Long,
reason: BinaryData) extends HtlcMessage with UpdateMessage
reason: BinaryData) extends HtlcMessage with UpdateMessage with HasChannelId
case class CommitSig(channelId: Long,
signature: BinaryData,
htlcSignatures: List[BinaryData]) extends HtlcMessage
htlcSignatures: List[BinaryData]) extends HtlcMessage with HasChannelId
case class RevokeAndAck(channelId: Long,
perCommitmentSecret: Scalar,
nextPerCommitmentPoint: Point,
htlcTimeoutSignatures: List[BinaryData]) extends HtlcMessage
htlcTimeoutSignatures: List[BinaryData]) extends HtlcMessage with HasChannelId
case class UpdateFee(channelId: Long,
feeratePerKw: Long) extends ChannelMessage with UpdateMessage
feeratePerKw: Long) extends ChannelMessage with UpdateMessage with HasChannelId
case class ChannelAnnouncement(nodeSignature1: BinaryData,
nodeSignature2: BinaryData,
@ -130,7 +132,7 @@ case class ChannelUpdate(signature: BinaryData,
case class AnnouncementSignatures(channelId: Long,
nodeSignature: BinaryData,
bitcoinSignature: BinaryData) extends RoutingMessage
bitcoinSignature: BinaryData) extends RoutingMessage with HasChannelId
case class PerHopPayload(amt_to_forward: Long,
outgoing_cltv_value: Int)

View File

@ -9,7 +9,7 @@ import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair._
import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.payment.Relayer
import fr.acinq.eclair.wire.UpdateAddHtlc
import fr.acinq.eclair.wire.{Init, UpdateAddHtlc}
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
@ -52,10 +52,13 @@ class ThroughputSpec extends FunSuite {
context.become(run(h2r - htlc.paymentHash))
}
}), "payment-handler")
val router: ActorRef = ???
val relayer = system.actorOf(Relayer.props(Globals.Node.privateKey, paymentHandler))
val alice = system.actorOf(Channel.props(pipe, blockchain, ???, relayer, Alice.channelParams, Bob.id), "a")
val bob = system.actorOf(Channel.props(pipe, blockchain, ???, relayer, Bob.channelParams, Alice.id), "b")
val alice = system.actorOf(Channel.props(pipe, blockchain, ???, relayer), "a")
val bob = system.actorOf(Channel.props(pipe, blockchain, ???, relayer), "b")
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)
val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures)
alice ! INPUT_INIT_FUNDER(Bob.id, 0, TestConstants.fundingSatoshis, TestConstants.pushMsat, Alice.channelParams, bobInit)
bob ! INPUT_INIT_FUNDEE(Alice.id, 0, Bob.channelParams, aliceInit)
val latch = new CountDownLatch(2)
val listener = system.actorOf(Props(new Actor {

View File

@ -4,6 +4,7 @@ import akka.actor.ActorRef
import akka.testkit.{TestFSMRef, TestKitBase, TestProbe}
import fr.acinq.bitcoin.{BinaryData, Crypto}
import fr.acinq.eclair.TestConstants
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.channel._
import fr.acinq.eclair.wire._
@ -22,12 +23,10 @@ trait StateTestsHelperMethods extends TestKitBase {
blockchainA: ActorRef,
alice2blockchain: TestProbe,
bob2blockchain: TestProbe): Unit = {
alice ! INPUT_INIT_FUNDER(TestConstants.fundingSatoshis, TestConstants.pushMsat)
bob ! INPUT_INIT_FUNDEE()
alice2bob.expectMsgType[Init]
alice2bob.forward(bob)
bob2alice.expectMsgType[Init]
bob2alice.forward(alice)
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)
val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures)
alice ! INPUT_INIT_FUNDER(Bob.id, 0, TestConstants.fundingSatoshis, TestConstants.pushMsat, Alice.channelParams, bobInit)
bob ! INPUT_INIT_FUNDEE(Alice.id, 0, Bob.channelParams, aliceInit)
alice2bob.expectMsgType[OpenChannel]
alice2bob.forward(bob)
bob2alice.expectMsgType[AcceptChannel]

View File

@ -28,15 +28,13 @@ class WaitForAcceptChannelStateSpec extends TestkitBaseClass {
val bob2blockchain = TestProbe()
val relayer = TestProbe()
val router = TestProbe()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref, Alice.channelParams, Bob.id))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref, Bob.channelParams, Alice.id))
alice ! INPUT_INIT_FUNDER(TestConstants.fundingSatoshis, TestConstants.pushMsat)
bob ! INPUT_INIT_FUNDEE()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref))
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)
val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures)
within(30 seconds) {
alice2bob.expectMsgType[Init]
alice2bob.forward(bob)
bob2alice.expectMsgType[Init]
bob2alice.forward(alice)
alice ! INPUT_INIT_FUNDER(Bob.id, 0, TestConstants.fundingSatoshis, TestConstants.pushMsat, Alice.channelParams, bobInit)
bob ! INPUT_INIT_FUNDEE(Alice.id, 0, Bob.channelParams, aliceInit)
alice2bob.expectMsgType[OpenChannel]
alice2bob.forward(bob)
awaitCond(alice.stateName == WAIT_FOR_ACCEPT_CHANNEL)

View File

@ -1,10 +1,10 @@
package fr.acinq.eclair.channel.states.a
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.wire.{Error, Init, OpenChannel}
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
@ -25,15 +25,13 @@ class WaitForOpenChannelStateSpec extends TestkitBaseClass {
val bob2blockchain = TestProbe()
val relayer = TestProbe()
val router = TestProbe()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref, Alice.channelParams, Bob.id))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref, Bob.channelParams, Alice.id))
alice ! INPUT_INIT_FUNDER(TestConstants.fundingSatoshis, TestConstants.pushMsat)
bob ! INPUT_INIT_FUNDEE()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref))
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)
val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures)
within(30 seconds) {
alice2bob.expectMsgType[Init]
alice2bob.forward(bob)
bob2alice.expectMsgType[Init]
bob2alice.forward(alice)
alice ! INPUT_INIT_FUNDER(Bob.id, 0, TestConstants.fundingSatoshis, TestConstants.pushMsat, Alice.channelParams, bobInit)
bob ! INPUT_INIT_FUNDEE(Alice.id, 0, Bob.channelParams, aliceInit)
awaitCond(bob.stateName == WAIT_FOR_OPEN_CHANNEL)
}
test((bob, alice2bob, bob2alice, bob2blockchain))

View File

@ -28,15 +28,13 @@ class WaitForFundingCreatedInternalStateSpec extends TestkitBaseClass {
val bob2blockchain = TestProbe()
val relayer = TestProbe()
val router = TestProbe()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref, Alice.channelParams, Bob.id))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref, Bob.channelParams, Alice.id))
alice ! INPUT_INIT_FUNDER(TestConstants.fundingSatoshis, TestConstants.pushMsat)
bob ! INPUT_INIT_FUNDEE()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref))
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)
val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures)
within(30 seconds) {
alice2bob.expectMsgType[Init]
alice2bob.forward(bob)
bob2alice.expectMsgType[Init]
bob2alice.forward(alice)
alice ! INPUT_INIT_FUNDER(Bob.id, 0, TestConstants.fundingSatoshis, TestConstants.pushMsat, Alice.channelParams, bobInit)
bob ! INPUT_INIT_FUNDEE(Alice.id, 0, Bob.channelParams, aliceInit)
alice2bob.expectMsgType[OpenChannel]
alice2bob.forward(bob)
bob2alice.expectMsgType[AcceptChannel]

View File

@ -6,7 +6,7 @@ import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain.{PeerWatcher, WatchConfirmed, WatchSpent}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.wire._
import fr.acinq.eclair.{TestkitBaseClass, TestBitcoinClient, TestConstants}
import fr.acinq.eclair.{TestBitcoinClient, TestConstants, TestkitBaseClass}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
@ -27,15 +27,13 @@ class WaitForFundingCreatedStateSpec extends TestkitBaseClass {
val bob2blockchain = TestProbe()
val relayer = TestProbe()
val router = TestProbe()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, blockchainA, router.ref, relayer.ref, Alice.channelParams, Bob.id))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref, Bob.channelParams, Alice.id))
alice ! INPUT_INIT_FUNDER(TestConstants.fundingSatoshis, TestConstants.pushMsat)
bob ! INPUT_INIT_FUNDEE()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, blockchainA, router.ref, relayer.ref))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref))
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)
val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures)
within(30 seconds) {
alice2bob.expectMsgType[Init]
alice2bob.forward(bob)
bob2alice.expectMsgType[Init]
bob2alice.forward(alice)
alice ! INPUT_INIT_FUNDER(Bob.id, 0, TestConstants.fundingSatoshis, TestConstants.pushMsat, Alice.channelParams, bobInit)
bob ! INPUT_INIT_FUNDEE(Alice.id, 0, Bob.channelParams, aliceInit)
alice2bob.expectMsgType[OpenChannel]
alice2bob.forward(bob)
bob2alice.expectMsgType[AcceptChannel]

View File

@ -29,15 +29,13 @@ class WaitForFundingSignedStateSpec extends TestkitBaseClass {
val bob2blockchain = TestProbe()
val relayer = TestProbe()
val router = TestProbe()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref, Alice.channelParams, Bob.id))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref, Bob.channelParams, Alice.id))
alice ! INPUT_INIT_FUNDER(TestConstants.fundingSatoshis, TestConstants.pushMsat)
bob ! INPUT_INIT_FUNDEE()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref))
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)
val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures)
within(30 seconds) {
alice2bob.expectMsgType[Init]
alice2bob.forward(bob)
bob2alice.expectMsgType[Init]
bob2alice.forward(alice)
alice ! INPUT_INIT_FUNDER(Bob.id, 0, TestConstants.fundingSatoshis, TestConstants.pushMsat, Alice.channelParams, bobInit)
bob ! INPUT_INIT_FUNDEE(Alice.id, 0, Bob.channelParams, aliceInit)
alice2bob.expectMsgType[OpenChannel]
alice2bob.forward(bob)
bob2alice.expectMsgType[AcceptChannel]

View File

@ -8,7 +8,6 @@ import fr.acinq.eclair.channel._
import fr.acinq.eclair.wire._
import fr.acinq.eclair.{TestBitcoinClient, TestConstants, TestkitBaseClass}
import org.junit.runner.RunWith
import org.scalatest.Tag
import org.scalatest.junit.JUnitRunner
import scala.concurrent.duration._
@ -29,18 +28,14 @@ class WaitForAnnSignaturesStateSpec extends TestkitBaseClass {
val bob2blockchain = TestProbe()
val relayer = TestProbe()
val router = TestProbe()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref))
val (aliceParams, bobParams) = (Alice.channelParams.copy(localFeatures = "01"), Bob.channelParams.copy(localFeatures = "01"))
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref, aliceParams, Bob.id))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref, bobParams, Alice.id))
alice ! INPUT_INIT_FUNDER(TestConstants.fundingSatoshis, TestConstants.pushMsat)
bob ! INPUT_INIT_FUNDEE()
val aliceInit = Init(aliceParams.globalFeatures, aliceParams.localFeatures)
val bobInit = Init(bobParams.globalFeatures, bobParams.localFeatures)
within(30 seconds) {
alice2bob.expectMsgType[Init]
alice2bob.forward(bob)
bob2alice.expectMsgType[Init]
bob2alice.forward(alice)
alice ! INPUT_INIT_FUNDER(Bob.id, 0, TestConstants.fundingSatoshis, TestConstants.pushMsat, aliceParams, bobInit)
bob ! INPUT_INIT_FUNDEE(Alice.id, 0, bobParams, aliceInit)
alice2bob.expectMsgType[OpenChannel]
alice2bob.forward(bob)
bob2alice.expectMsgType[AcceptChannel]

View File

@ -28,15 +28,13 @@ class WaitForFundingLockedInternalStateSpec extends TestkitBaseClass {
val bob2blockchain = TestProbe()
val relayer = TestProbe()
val router = TestProbe()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref, Alice.channelParams, Bob.id))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref, Bob.channelParams, Alice.id))
alice ! INPUT_INIT_FUNDER(TestConstants.fundingSatoshis, TestConstants.pushMsat)
bob ! INPUT_INIT_FUNDEE()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref))
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)
val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures)
within(30 seconds) {
alice2bob.expectMsgType[Init]
alice2bob.forward(bob)
bob2alice.expectMsgType[Init]
bob2alice.forward(alice)
alice ! INPUT_INIT_FUNDER(Bob.id, 0, TestConstants.fundingSatoshis, TestConstants.pushMsat, Alice.channelParams, bobInit)
bob ! INPUT_INIT_FUNDEE(Alice.id, 0, Bob.channelParams, aliceInit)
alice2bob.expectMsgType[OpenChannel]
alice2bob.forward(bob)
bob2alice.expectMsgType[AcceptChannel]

View File

@ -2,15 +2,13 @@ package fr.acinq.eclair.channel.states.c
import akka.actor.{ActorRef, Props}
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.bitcoin.{BinaryData, Crypto}
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.channel._
import fr.acinq.eclair.router.{Announcements, Router}
import fr.acinq.eclair.wire._
import fr.acinq.eclair.{TestBitcoinClient, TestConstants, TestkitBaseClass}
import org.junit.runner.RunWith
import org.scalatest.{ConfigMap, Tag, TestData}
import org.scalatest.Tag
import org.scalatest.junit.JUnitRunner
import scala.concurrent.duration._
@ -31,22 +29,18 @@ class WaitForFundingLockedStateSpec extends TestkitBaseClass {
val bob2blockchain = TestProbe()
val relayer = TestProbe()
val router = TestProbe()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref))
val (aliceParams, bobParams) = if (test.tags.contains("public")) {
(Alice.channelParams.copy(localFeatures = "01"), Bob.channelParams.copy(localFeatures = "01"))
} else {
(Alice.channelParams, Bob.channelParams)
}
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref, aliceParams, Bob.id))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref, bobParams, Alice.id))
alice ! INPUT_INIT_FUNDER(TestConstants.fundingSatoshis, TestConstants.pushMsat)
bob ! INPUT_INIT_FUNDEE()
val aliceInit = Init(aliceParams.globalFeatures, aliceParams.localFeatures)
val bobInit = Init(bobParams.globalFeatures, bobParams.localFeatures)
within(30 seconds) {
alice2bob.expectMsgType[Init]
alice2bob.forward(bob)
bob2alice.expectMsgType[Init]
bob2alice.forward(alice)
alice ! INPUT_INIT_FUNDER(Bob.id, 0, TestConstants.fundingSatoshis, TestConstants.pushMsat, aliceParams, bobInit)
bob ! INPUT_INIT_FUNDEE(Alice.id, 0, bobParams, aliceInit)
alice2bob.expectMsgType[OpenChannel]
alice2bob.forward(bob)
bob2alice.expectMsgType[AcceptChannel]
@ -79,6 +73,7 @@ class WaitForFundingLockedStateSpec extends TestkitBaseClass {
bob2alice.expectMsgType[FundingLocked]
bob2alice.forward(alice)
awaitCond(alice.stateName == NORMAL)
bob2alice.expectNoMsg(200 millis)
}
}

View File

@ -34,8 +34,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val bob2blockchain = TestProbe()
val relayer = TestProbe()
val router = TestProbe()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref, Alice.channelParams, Bob.id))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref, Bob.channelParams, Alice.id))
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref))
within(30 seconds) {
reachNormal(alice, bob, alice2bob, bob2alice, blockchainA, alice2blockchain, bob2blockchain)
awaitCond(alice.stateName == NORMAL)

View File

@ -32,8 +32,8 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val bob2blockchain = TestProbe()
val relayer = TestProbe()
val router = TestProbe()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref, Alice.channelParams, Bob.id))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref, Bob.channelParams, Alice.id))
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref))
within(30 seconds) {
reachNormal(alice, bob, alice2bob, bob2alice, blockchainA, alice2blockchain, bob2blockchain)
val sender = TestProbe()

View File

@ -30,8 +30,8 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods
val relayer = TestProbe()
// note that alice.initialFeeRate != bob.initialFeeRate
val router = TestProbe()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref, Alice.channelParams, Bob.id))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref, Bob.channelParams, Alice.id))
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref))
within(30 seconds) {
reachNormal(alice, bob, alice2bob, bob2alice, blockchainA, alice2blockchain, bob2blockchain)
val sender = TestProbe()

View File

@ -30,8 +30,8 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val bob2blockchain = TestProbe()
val relayer = TestProbe()
val router = TestProbe()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref, Alice.channelParams, Bob.id))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref, Bob.channelParams, Alice.id))
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref))
within(30 seconds) {
reachNormal(alice, bob, alice2bob, bob2alice, blockchainA, alice2blockchain, bob2blockchain)

View File

@ -7,7 +7,7 @@ import akka.io.Tcp.{Received, Write}
import akka.testkit.{TestActorRef, TestFSMRef, TestKit, TestProbe}
import fr.acinq.bitcoin.{Base58Check, BinaryData}
import fr.acinq.eclair.crypto.Noise.{Chacha20Poly1305CipherFunctions, CipherState}
import fr.acinq.eclair.crypto.TransportHandler.ExtendedCipherState
import fr.acinq.eclair.crypto.TransportHandler.{ExtendedCipherState, Listener}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
@ -32,10 +32,16 @@ class TransportHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLik
val pipe = system.actorOf(Props[MyPipe])
val probe1 = TestProbe()
val probe2 = TestProbe()
val initiator = TestFSMRef(new TransportHandler(Initiator.s, Some(Responder.s.pub), pipe, true, (conn, _, _) => probe1.ref, TransportHandler.Noop))
val responder = TestFSMRef(new TransportHandler(Responder.s, None, pipe, false, (conn, _, _) => probe2.ref, TransportHandler.Noop))
val initiator = TestFSMRef(new TransportHandler(Initiator.s, Some(Responder.s.pub), pipe, TransportHandler.Noop))
val responder = TestFSMRef(new TransportHandler(Responder.s, None, pipe, TransportHandler.Noop))
pipe ! (initiator, responder)
awaitCond(initiator.stateName == TransportHandler.WaitingForListener)
awaitCond(responder.stateName == TransportHandler.WaitingForListener)
initiator ! Listener(probe1.ref)
responder ! Listener(probe2.ref)
awaitCond(initiator.stateName == TransportHandler.WaitingForCyphertext)
awaitCond(responder.stateName == TransportHandler.WaitingForCyphertext)
@ -68,10 +74,16 @@ class TransportHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLik
val pipe = system.actorOf(Props[MyPipe])
val probe1 = TestProbe()
val probe2 = TestProbe()
val initiator = TestFSMRef(new TransportHandler(Initiator.s, Some(Responder.s.pub), pipe, true, (conn, _, _) => probe1.ref, mySerializer))
val responder = TestFSMRef(new TransportHandler(Responder.s, None, pipe, false, (conn, _, _) => probe2.ref, mySerializer))
val initiator = TestFSMRef(new TransportHandler(Initiator.s, Some(Responder.s.pub), pipe, mySerializer))
val responder = TestFSMRef(new TransportHandler(Responder.s, None, pipe, mySerializer))
pipe ! (initiator, responder)
awaitCond(initiator.stateName == TransportHandler.WaitingForListener)
awaitCond(responder.stateName == TransportHandler.WaitingForListener)
initiator ! Listener(probe1.ref)
responder ! Listener(probe2.ref)
awaitCond(initiator.stateName == TransportHandler.WaitingForCyphertext)
awaitCond(responder.stateName == TransportHandler.WaitingForCyphertext)
@ -92,10 +104,16 @@ class TransportHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLik
val pipe = system.actorOf(Props[MyPipeSplitter])
val probe1 = TestProbe()
val probe2 = TestProbe()
val initiator = TestFSMRef(new TransportHandler(Initiator.s, Some(Responder.s.pub), pipe, true, (conn, _, _) => probe1.ref, TransportHandler.Noop))
val responder = TestFSMRef(new TransportHandler(Responder.s, None, pipe, false, (conn, _, _) => probe2.ref, TransportHandler.Noop))
val initiator = TestFSMRef(new TransportHandler(Initiator.s, Some(Responder.s.pub), pipe, TransportHandler.Noop))
val responder = TestFSMRef(new TransportHandler(Responder.s, None, pipe, TransportHandler.Noop))
pipe ! (initiator, responder)
awaitCond(initiator.stateName == TransportHandler.WaitingForListener)
awaitCond(responder.stateName == TransportHandler.WaitingForListener)
initiator ! Listener(probe1.ref)
responder ! Listener(probe2.ref)
awaitCond(initiator.stateName == TransportHandler.WaitingForCyphertext)
awaitCond(responder.stateName == TransportHandler.WaitingForCyphertext)
@ -117,8 +135,8 @@ class TransportHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLik
val probe1 = TestProbe()
val probe2 = TestProbe()
val supervisor = TestActorRef(Props(new MySupervisor()))
val initiator = TestFSMRef(new TransportHandler(Initiator.s, Some(Initiator.s.pub), pipe, true, (conn, _, _) => probe1.ref, TransportHandler.Noop), supervisor, "ini")
val responder = TestFSMRef(new TransportHandler(Responder.s, None, pipe, false, (conn, _, _) => probe2.ref, TransportHandler.Noop), supervisor, "res")
val initiator = TestFSMRef(new TransportHandler(Initiator.s, Some(Initiator.s.pub), pipe, TransportHandler.Noop), supervisor, "ini")
val responder = TestFSMRef(new TransportHandler(Responder.s, None, pipe, TransportHandler.Noop), supervisor, "res")
probe1.watch(responder)
pipe ! (initiator, responder)

View File

@ -3,14 +3,12 @@ package fr.acinq.eclair.interop
import java.nio.file.{Files, Paths}
import akka.actor.{ActorRef, ActorSystem}
import akka.pattern.ask
import akka.util.Timeout
import com.typesafe.config.ConfigFactory
import fr.acinq.bitcoin.BinaryData
import fr.acinq.eclair._
import fr.acinq.eclair.blockchain.ExtendedBitcoinClient
import fr.acinq.eclair.blockchain.rpc.BitcoinJsonRPCClient
import fr.acinq.eclair.channel.Register.ListChannels
import fr.acinq.eclair.channel.{CLOSED, CLOSING, CMD_ADD_HTLC, _}
import org.json4s.JsonAST.JString
import org.json4s.jackson.JsonMethods._
@ -79,7 +77,7 @@ class InteroperabilitySpec extends FunSuite with BeforeAndAfterAll {
val setup = new Setup()
implicit val system = setup.system
val register = setup.register
//val register = setup.register
implicit val timeout = Timeout(30 seconds)
@ -106,11 +104,11 @@ class InteroperabilitySpec extends FunSuite with BeforeAndAfterAll {
future
}
def listChannels: Future[Iterable[RES_GETINFO]] = {
def listChannels: Future[Iterable[RES_GETINFO]] = ???/*{
implicit val timeout = Timeout(5 seconds)
(register ? ListChannels).mapTo[Iterable[ActorRef]]
.flatMap(l => Future.sequence(l.map(c => (c ? CMD_GETINFO).mapTo[RES_GETINFO])))
}
}*/
def waitForState(state: State): Future[Unit] = {
listChannels.map(_.map(_.state)).flatMap(current =>

View File

@ -8,8 +8,9 @@ import akka.testkit.{TestFSMRef, TestKit, TestProbe}
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain.PeerWatcher
import fr.acinq.eclair.channel._
import fr.acinq.eclair.payment.{NoopPaymentHandler, Relayer}
import fr.acinq.eclair.{Globals, TestBitcoinClient, TestConstants}
import fr.acinq.eclair.payment.NoopPaymentHandler
import fr.acinq.eclair.wire.Init
import fr.acinq.eclair.{Globals, TestBitcoinClient}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.{BeforeAndAfterAll, Matchers, fixture}
@ -35,11 +36,13 @@ class RustyTestsSpec extends TestKit(ActorSystem("test")) with Matchers with fix
// we just bypass the relayer for this test
val relayer = paymentHandler
val router = TestProbe()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(pipe, blockchainA, router.ref, relayer, Alice.channelParams, Bob.id))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(pipe, blockchainB, router.ref, relayer, Bob.channelParams, Alice.id))
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(pipe, blockchainA, router.ref, relayer))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(pipe, blockchainB, router.ref, relayer))
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)
val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures)
// alice and bob will both have 1 000 000 sat
alice ! INPUT_INIT_FUNDER(2000000, 1000000000)
bob ! INPUT_INIT_FUNDEE()
alice ! INPUT_INIT_FUNDER(Bob.id, 0, 2000000, 1000000000, Alice.channelParams, bobInit)
bob ! INPUT_INIT_FUNDEE(Alice.id, 0, Bob.channelParams, aliceInit)
pipe ! (alice, bob)
within(30 seconds) {
awaitCond(alice.stateName == NORMAL)