1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-02-21 14:04:10 +01:00

Merge branch 'wip-persist3-pm' into wip-bolts

This commit is contained in:
pm47 2017-02-28 14:19:53 +01:00
commit b4af63b728
37 changed files with 896 additions and 302 deletions

View file

@ -24,6 +24,9 @@ eclair {
b = 170
}
}
db {
root = "~/.eclair"
}
delay-blocks = 144
mindepth-blocks = 3
expiry-delta-blocks = 144

View file

@ -8,14 +8,17 @@ import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import akka.util.Timeout
import com.typesafe.config.ConfigFactory
import fr.acinq.bitcoin.{Base58Check, OP_CHECKSIG, OP_DUP, OP_EQUALVERIFY, OP_HASH160, OP_PUSHDATA, Script}
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin.{Base58Check, BinaryData, OP_CHECKSIG, OP_DUP, OP_EQUALVERIFY, OP_HASH160, OP_PUSHDATA, Script}
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.Register
import fr.acinq.eclair.channel.{Channel, Register}
import fr.acinq.eclair.crypto.TransportHandler.Serializer
import fr.acinq.eclair.db.{JavaSerializer, SimpleFileDb, SimpleTypedDb}
import fr.acinq.eclair.gui.FxApp
import fr.acinq.eclair.io.{Server, Switchboard}
import fr.acinq.eclair.io.{Peer, PeerRecord, Server, Switchboard}
import fr.acinq.eclair.payment._
import fr.acinq.eclair.router._
import grizzled.slf4j.Logging
@ -36,6 +39,7 @@ object Boot extends App with Logging {
logger.error(s"received fatal event $e")
Platform.exit()
})
s.boostrap
case _ => Application.launch(classOf[FxApp])
}
}
@ -60,7 +64,7 @@ class Setup() extends Logging {
implicit val formats = org.json4s.DefaultFormats
implicit val ec = ExecutionContext.Implicits.global
val (chain, blockCount, progress) = Await.result(bitcoin_client.client.invoke("getblockchaininfo").map(json => ((json \ "chain").extract[String], (json \ "blocks").extract[Long], (json \ "verificationprogress").extract[Double])), 10 seconds)
assert(chain == "testnet" || chain == "regtest" || chain == "segnet4", "you should be on testnet or regtest or segnet4")
assert(chain == "test" || chain == "regtest" || chain == "segnet4", "you should be on testnet or regtest or segnet4")
assert(progress > 0.99, "bitcoind should be synchronized")
Globals.blockCount.set(blockCount)
val bitcoinVersion = Await.result(bitcoin_client.client.invoke("getinfo").map(json => (json \ "version").extract[String]), 10 seconds)
@ -83,6 +87,16 @@ class Setup() extends Logging {
}))
val fatalEventFuture = fatalEventPromise.future
val db = nodeParams.db
val peerDb = Peer.makePeerDb(db)
val peers = peerDb.values
val channelDb = Channel.makeChannelDb(db)
val channels = channelDb.values
val routerDb = Router.makeRouterDb(db)
val routerStates = routerDb.values
val peer = system.actorOf(Props[PeerClient], "bitcoin-peer")
val watcher = system.actorOf(PeerWatcher.props(bitcoin_client), name = "watcher")
val paymentHandler = config.getString("eclair.payment-handler") match {
@ -91,9 +105,9 @@ class Setup() extends Logging {
}
val register = system.actorOf(Props(new Register), name = "register")
val relayer = system.actorOf(Relayer.props(nodeParams.privateKey, paymentHandler), name = "relayer")
val router = system.actorOf(Router.props(watcher), name = "router")
val paymentInitiator = system.actorOf(PaymentInitiator.props(nodeParams.privateKey.publicKey, router), "payment-initiator")
val router = system.actorOf(Router.props(watcher, db), name = "router")
val switchboard = system.actorOf(Switchboard.props(nodeParams, watcher, router, relayer, finalScriptPubKey), name = "switchboard")
val paymentInitiator = system.actorOf(PaymentInitiator.props(nodeParams.privateKey.publicKey, router), "payment-initiator")
val server = system.actorOf(Server.props(nodeParams, switchboard, new InetSocketAddress(config.getString("eclair.server.host"), config.getInt("eclair.server.port"))), "server")
val _setup = this
@ -108,4 +122,10 @@ class Setup() extends Logging {
Http().bindAndHandle(api.route, config.getString("eclair.api.host"), config.getInt("eclair.api.port")) onFailure {
case t: Throwable => system.eventStream.publish(HTTPBindError)
}
def boostrap: Unit = {
peers.map(rec => switchboard ! rec)
channels.map(rec => switchboard ! rec)
routerStates.map(rec => router ! rec)
}
}

View file

@ -6,6 +6,7 @@ import com.typesafe.config.ConfigFactory
import fr.acinq.bitcoin.{BinaryData, DeterministicWallet}
import fr.acinq.bitcoin.Crypto.PrivateKey
import fr.acinq.bitcoin.DeterministicWallet.ExtendedPrivateKey
import fr.acinq.eclair.db.{SimpleDb, SimpleFileDb}
/**
* Created by PM on 26/02/2017.
@ -28,7 +29,8 @@ case class NodeParams(extendedPrivateKey: ExtendedPrivateKey,
feeBaseMsat: Int,
feeProportionalMillionth: Int,
reserveToFundingRatio: Double,
maxReserveToFundingRatio: Double)
maxReserveToFundingRatio: Double,
db: SimpleDb)
object NodeParams {
@ -38,7 +40,7 @@ object NodeParams {
val seed: BinaryData = config.getString("node.seed")
val master = DeterministicWallet.generate(seed)
val extendedPrivateKey = DeterministicWallet.derivePrivateKey(master, DeterministicWallet.hardened(46) :: DeterministicWallet.hardened(0) :: Nil)
val db = new SimpleFileDb(config.getString("db.root"))
NodeParams(
extendedPrivateKey = extendedPrivateKey,
privateKey = extendedPrivateKey.privateKey,
@ -58,7 +60,8 @@ object NodeParams {
feeBaseMsat = config.getInt("fee-base-msat"),
feeProportionalMillionth = config.getInt("fee-proportional-millionth"),
reserveToFundingRatio = 0.01, // recommended by BOLT #2
maxReserveToFundingRatio = 0.05 // channel reserve can't be more than 5% of the funding amount (recommended: 1%)
maxReserveToFundingRatio = 0.05, // channel reserve can't be more than 5% of the funding amount (recommended: 1%)
db = db
)
}
}

View file

@ -81,6 +81,19 @@ class PeerWatcher(client: ExtendedBitcoinClient)(implicit ec: ExecutionContext =
}
}
case w@WatchConfirmed(channel, txId, minDepth, event) => {
client.getTxConfirmations(txId.toString).map {
case Some(confirmations) if confirmations >= minDepth =>
client.getTransactionShortId(txId.toString).map {
// TODO: this is a workaround to not have WatchConfirmed triggered multiple times in testing
// the reason is that we cannot do a become(watches - w, ...) because it happens in the future callback
case (height, index) => self ! ('trigger, w, WatchEventConfirmed(w.event, height, index))
}
case _ => ()
}
addWatch(w, watches, block2tx, lastTxes)
}
case w: Watch => addWatch(w, watches, block2tx, lastTxes)
case PublishAsap(tx) =>

View file

@ -16,7 +16,7 @@ class PeerClient extends Actor with ActorLogging {
val config = ConfigFactory.load().getConfig("eclair.bitcoind")
val magic = config.getString("network") match {
case "mainnet" => Message.MagicMain
case "testnet" => Message.MagicTestnet3
case "test" => Message.MagicTestnet3
case "regtest" => Message.MagicTestNet
}
val peer = new InetSocketAddress(config.getString("host"), config.getInt("port"))

View file

@ -7,7 +7,9 @@ import fr.acinq.eclair._
import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.blockchain.peer.CurrentBlockCount
import fr.acinq.eclair.channel.Helpers.{Closing, Funding}
import fr.acinq.eclair.crypto.TransportHandler.Serializer
import fr.acinq.eclair.crypto.{Generators, ShaChain}
import fr.acinq.eclair.db.{ChannelState, JavaSerializer, SimpleDb, SimpleTypedDb}
import fr.acinq.eclair.payment._
import fr.acinq.eclair.router.Announcements
import fr.acinq.eclair.transactions._
@ -18,6 +20,7 @@ import scala.concurrent.ExecutionContext
import scala.concurrent.duration._
import scala.util.{Failure, Left, Success, Try}
case class ChannelRecord(id: Long, state: ChannelState)
/**
* Created by PM on 20/08/2015.
@ -25,10 +28,31 @@ import scala.util.{Failure, Left, Success, Try}
object Channel {
def props(nodeParams: NodeParams, remote: ActorRef, blockchain: ActorRef, router: ActorRef, relayer: ActorRef) = Props(new Channel(nodeParams, remote, blockchain, router, relayer))
def makeChannelDb(db: SimpleDb): SimpleTypedDb[Long, ChannelRecord] = {
def channelid2String(id: Long) = s"channel-$id"
def string2channelid(s: String) = if (s.startsWith("channel-")) Some(s.stripPrefix("channel-").toLong) else None
new SimpleTypedDb[Long, ChannelRecord](
channelid2String,
string2channelid,
new Serializer[ChannelRecord] {
override def serialize(t: ChannelRecord): BinaryData = JavaSerializer.serialize(t)
override def deserialize(bin: BinaryData): ChannelRecord = JavaSerializer.deserialize[ChannelRecord](bin)
},
db
)
}
}
class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef, router: ActorRef, relayer: ActorRef)(implicit ec: ExecutionContext = ExecutionContext.Implicits.global) extends LoggingFSM[State, Data] {
import Channel._
val forwarder = context.actorOf(Props(new Forwarder(nodeParams)), "forwarder")
var remote = r
var remoteNodeId: PublicKey = null
@ -72,7 +96,7 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
when(WAIT_FOR_INIT_INTERNAL)(handleExceptions {
case Event(initFunder@INPUT_INIT_FUNDER(remoteNodeId, temporaryChannelId, fundingSatoshis, pushMsat, localParams, remoteInit), Nothing) =>
this.remoteNodeId = remoteNodeId
context.system.eventStream.publish(ChannelCreated(temporaryChannelId, context.parent, self, localParams, remoteNodeId))
context.system.eventStream.publish(ChannelCreated(self, context.parent, remoteNodeId, true, temporaryChannelId))
val firstPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, 0)
val open = OpenChannel(temporaryChannelId = temporaryChannelId,
fundingSatoshis = fundingSatoshis,
@ -95,17 +119,34 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
case Event(inputFundee@INPUT_INIT_FUNDEE(remoteNodeId, _, localParams, _), Nothing) if !localParams.isFunder =>
this.remoteNodeId = remoteNodeId
goto(WAIT_FOR_OPEN_CHANNEL) using DATA_WAIT_FOR_OPEN_CHANNEL(inputFundee)
case Event(INPUT_RESTORED(channelId, cs@ChannelState(remotePubKey, state, data)), _) =>
log.info(s"restoring channel $cs")
remoteNodeId = remotePubKey
context.system.eventStream.publish(ChannelRestored(self, context.parent, remoteNodeId, Helpers.getLocalParams(data).isFunder, Helpers.getChannelId(data), data))
data match {
case d: HasCommitments =>
blockchain ! WatchSpent(self, d.commitments.commitInput.outPoint.txid, d.commitments.commitInput.outPoint.index.toInt, BITCOIN_FUNDING_SPENT) // TODO: should we wait for an acknowledgment from the watcher?
Register.createAlias(remoteNodeId, d.commitments.channelId)
d match {
case closing: DATA_CLOSING if closing.remoteCommitPublished.isDefined =>
handleRemoteSpentCurrent(closing.remoteCommitPublished.get.commitTx, closing)
case _ => ()
}
case _ => ()
}
goto(OFFLINE) using data
})
when(WAIT_FOR_OPEN_CHANNEL)(handleExceptions {
case Event(open: OpenChannel, DATA_WAIT_FOR_OPEN_CHANNEL(INPUT_INIT_FUNDEE(_, _, localParams, remoteInit))) =>
Try(Funding.validateParams(nodeParams, open.channelReserveSatoshis, open.fundingSatoshis)) match {
Try(Helpers.validateParams(nodeParams, open.channelReserveSatoshis, open.fundingSatoshis)) match {
case Failure(t) =>
log.warning(t.getMessage)
remote ! Error(open.temporaryChannelId, t.getMessage.getBytes)
goto(CLOSED)
case Success(_) =>
context.system.eventStream.publish(ChannelCreated(open.temporaryChannelId, context.parent, self, localParams, remoteNodeId))
context.system.eventStream.publish(ChannelCreated(self, context.parent, remoteNodeId, false, open.temporaryChannelId))
// TODO: maybe also check uniqueness of temporary channel id
val minimumDepth = nodeParams.minDepthBlocks
val firstPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, 0)
@ -138,12 +179,8 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
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
remoteParams = remoteParams,
fundingSatoshis = open.fundingSatoshis,
minimumDepth = minimumDepth)
goto(WAIT_FOR_FUNDING_CREATED) using DATA_WAIT_FOR_FUNDING_CREATED(open.temporaryChannelId, params, open.pushMsat, open.firstPerCommitmentPoint, accept)
val localParams1 = localParams.copy(feeratePerKw = open.feeratePerKw) // funder gets to choose the first feerate
goto(WAIT_FOR_FUNDING_CREATED) using DATA_WAIT_FOR_FUNDING_CREATED(open.temporaryChannelId, localParams1, remoteParams, open.fundingSatoshis, open.pushMsat, open.firstPerCommitmentPoint, accept)
}
case Event(CMD_CLOSE(_), _) => goto(CLOSED)
@ -153,7 +190,7 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
when(WAIT_FOR_ACCEPT_CHANNEL)(handleExceptions {
case Event(accept: AcceptChannel, DATA_WAIT_FOR_ACCEPT_CHANNEL(INPUT_INIT_FUNDER(_, temporaryChannelId, fundingSatoshis, pushMsat, localParams, remoteInit), open)) =>
Try(Funding.validateParams(nodeParams, accept.channelReserveSatoshis, fundingSatoshis)) match {
Try(Helpers.validateParams(nodeParams, accept.channelReserveSatoshis, fundingSatoshis)) match {
case Failure(t) =>
log.warning(t.getMessage)
remote ! Error(temporaryChannelId, t.getMessage.getBytes)
@ -175,14 +212,9 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
globalFeatures = remoteInit.globalFeatures,
localFeatures = remoteInit.localFeatures)
log.debug(s"remote params: $remoteParams")
val params = ChannelParams(
localParams = localParams,
remoteParams = remoteParams,
fundingSatoshis = fundingSatoshis,
minimumDepth = accept.minimumDepth)
val localFundingPubkey = params.localParams.fundingPrivKey.publicKey
blockchain ! MakeFundingTx(localFundingPubkey, remoteParams.fundingPubKey, Satoshi(params.fundingSatoshis))
goto(WAIT_FOR_FUNDING_INTERNAL) using DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId, params, pushMsat, accept.firstPerCommitmentPoint, open)
val localFundingPubkey = localParams.fundingPrivKey.publicKey
blockchain ! MakeFundingTx(localFundingPubkey, remoteParams.fundingPubKey, Satoshi(fundingSatoshis))
goto(WAIT_FOR_FUNDING_INTERNAL) using DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, accept.firstPerCommitmentPoint, open)
}
case Event(CMD_CLOSE(_), _) => goto(CLOSED)
@ -191,14 +223,14 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
})
when(WAIT_FOR_FUNDING_INTERNAL)(handleExceptions {
case Event(MakeFundingTxResponse(fundingTx: Transaction, fundingTxOutputIndex: Int), DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId, params, pushMsat, remoteFirstPerCommitmentPoint, _)) =>
case Event(MakeFundingTxResponse(fundingTx: Transaction, fundingTxOutputIndex: Int), DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, remoteFirstPerCommitmentPoint, _)) =>
// our wallet provided us with a funding tx
log.info(s"funding tx txid=${fundingTx.txid}")
// 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 (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(localParams, remoteParams, fundingSatoshis, pushMsat, fundingTx.hash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint)
val localSigOfRemoteTx = Transactions.sign(remoteCommitTx, params.localParams.fundingPrivKey)
val localSigOfRemoteTx = Transactions.sign(remoteCommitTx, localParams.fundingPrivKey)
// signature of their initial commitment tx that pays remote pushMsat
val fundingCreated = FundingCreated(
temporaryChannelId = temporaryChannelId,
@ -207,7 +239,7 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
signature = localSigOfRemoteTx
)
remote ! fundingCreated
goto(WAIT_FOR_FUNDING_SIGNED) using DATA_WAIT_FOR_FUNDING_SIGNED(temporaryChannelId, params, fundingTx, localSpec, localCommitTx, RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, remoteFirstPerCommitmentPoint), fundingCreated)
goto(WAIT_FOR_FUNDING_SIGNED) using DATA_WAIT_FOR_FUNDING_SIGNED(temporaryChannelId, localParams, remoteParams, fundingTx, localSpec, localCommitTx, RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, remoteFirstPerCommitmentPoint), fundingCreated)
case Event(CMD_CLOSE(_), _) => goto(CLOSED)
@ -215,13 +247,13 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
})
when(WAIT_FOR_FUNDING_CREATED)(handleExceptions {
case Event(FundingCreated(_, fundingTxHash, fundingTxOutputIndex, remoteSig), DATA_WAIT_FOR_FUNDING_CREATED(temporaryChannelId, params, pushMsat, remoteFirstPerCommitmentPoint, _)) =>
case Event(FundingCreated(_, fundingTxHash, fundingTxOutputIndex, remoteSig), DATA_WAIT_FOR_FUNDING_CREATED(temporaryChannelId, localParams, remoteParams, fundingSatoshis: Long, pushMsat, remoteFirstPerCommitmentPoint, _)) =>
// they fund the channel with their funding tx, so the money is theirs (but we are paid pushMsat)
val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(params, pushMsat, fundingTxHash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint)
val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(localParams, remoteParams, fundingSatoshis: Long, pushMsat, fundingTxHash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint)
// check remote signature validity
val localSigOfLocalTx = Transactions.sign(localCommitTx, params.localParams.fundingPrivKey)
val signedLocalCommitTx = Transactions.addSigs(localCommitTx, params.localParams.fundingPrivKey.publicKey, params.remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig)
val localSigOfLocalTx = Transactions.sign(localCommitTx, localParams.fundingPrivKey)
val signedLocalCommitTx = Transactions.addSigs(localCommitTx, localParams.fundingPrivKey.publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig)
Transactions.checkSpendable(signedLocalCommitTx) match {
case Failure(cause) =>
log.error(cause, "their FundingCreated message contains an invalid signature")
@ -230,7 +262,7 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
goto(CLOSED)
case Success(_) =>
log.info(s"signing remote tx: $remoteCommitTx")
val localSigOfRemoteTx = Transactions.sign(remoteCommitTx, params.localParams.fundingPrivKey)
val localSigOfRemoteTx = Transactions.sign(remoteCommitTx, localParams.fundingPrivKey)
val fundingSigned = FundingSigned(
temporaryChannelId = temporaryChannelId,
signature = localSigOfRemoteTx
@ -240,17 +272,17 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
// watch the funding tx transaction
val commitInput = localCommitTx.input
blockchain ! WatchSpent(self, commitInput.outPoint.txid, commitInput.outPoint.index.toInt, BITCOIN_FUNDING_SPENT) // TODO: should we wait for an acknowledgment from the watcher?
blockchain ! WatchConfirmed(self, commitInput.outPoint.txid, params.minimumDepth.toInt, BITCOIN_FUNDING_DEPTHOK)
blockchain ! WatchConfirmed(self, commitInput.outPoint.txid, nodeParams.minDepthBlocks, BITCOIN_FUNDING_DEPTHOK)
val commitments = Commitments(params.localParams, params.remoteParams,
val commitments = Commitments(localParams, remoteParams,
LocalCommit(0, localSpec, PublishableTxs(signedLocalCommitTx, Nil), null), RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, remoteFirstPerCommitmentPoint),
LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil),
localNextHtlcId = 0L, remoteNextHtlcId = 0L,
remoteNextCommitInfo = Right(null), // TODO: we will receive their next per-commitment point in the next message, so we temporarily put an empty byte array,
unackedMessages = Nil,
commitInput, ShaChain.init, channelId = 0) // TODO: we will compute the channelId at the next step, so we temporarily put 0
context.system.eventStream.publish(ChannelIdAssigned(self, commitments.anchorId, Satoshi(params.fundingSatoshis)))
goto(WAIT_FOR_FUNDING_CONFIRMED) using DATA_WAIT_FOR_FUNDING_CONFIRMED(temporaryChannelId, params, commitments, None, Right(fundingSigned))
commitInput, ShaChain.init, channelId = temporaryChannelId) // TODO: we will compute the channelId at the next step, so we temporarily put 0
context.system.eventStream.publish(ChannelSignatureReceived(self, commitments))
goto(WAIT_FOR_FUNDING_CONFIRMED) using DATA_WAIT_FOR_FUNDING_CONFIRMED(temporaryChannelId, commitments, None, Right(fundingSigned))
}
case Event(CMD_CLOSE(_), _) => goto(CLOSED)
@ -259,10 +291,10 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
})
when(WAIT_FOR_FUNDING_SIGNED)(handleExceptions {
case Event(FundingSigned(_, remoteSig), DATA_WAIT_FOR_FUNDING_SIGNED(temporaryChannelId, params, fundingTx, localSpec, localCommitTx, remoteCommit, fundingCreated)) =>
case Event(FundingSigned(_, remoteSig), DATA_WAIT_FOR_FUNDING_SIGNED(temporaryChannelId, localParams, remoteParams, fundingTx, localSpec, localCommitTx, remoteCommit, fundingCreated)) =>
// we make sure that their sig checks out and that our first commit tx is spendable
val localSigOfLocalTx = Transactions.sign(localCommitTx, params.localParams.fundingPrivKey)
val signedLocalCommitTx = Transactions.addSigs(localCommitTx, params.localParams.fundingPrivKey.publicKey, params.remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig)
val localSigOfLocalTx = Transactions.sign(localCommitTx, localParams.fundingPrivKey)
val signedLocalCommitTx = Transactions.addSigs(localCommitTx, localParams.fundingPrivKey.publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig)
Transactions.checkSpendable(signedLocalCommitTx) match {
case Failure(cause) =>
log.error(cause, "their FundingSigned message contains an invalid signature")
@ -272,19 +304,18 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
case Success(_) =>
val commitInput = localCommitTx.input
blockchain ! WatchSpent(self, commitInput.outPoint.txid, commitInput.outPoint.index.toInt, BITCOIN_FUNDING_SPENT) // TODO: should we wait for an acknowledgment from the watcher?
blockchain ! WatchConfirmed(self, commitInput.outPoint.txid, params.minimumDepth, BITCOIN_FUNDING_DEPTHOK)
blockchain ! WatchConfirmed(self, commitInput.outPoint.txid, nodeParams.minDepthBlocks, BITCOIN_FUNDING_DEPTHOK)
blockchain ! PublishAsap(fundingTx)
val commitments = Commitments(params.localParams, params.remoteParams,
val commitments = Commitments(localParams, remoteParams,
LocalCommit(0, localSpec, PublishableTxs(signedLocalCommitTx, Nil), null), remoteCommit,
LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil),
localNextHtlcId = 0L, remoteNextHtlcId = 0L,
remoteNextCommitInfo = Right(null), // TODO: we will receive their next per-commitment point in the next message, so we temporarily put an empty byte array
unackedMessages = Nil,
commitInput, ShaChain.init, channelId = 0)
context.system.eventStream.publish(ChannelIdAssigned(self, commitments.anchorId, Satoshi(params.fundingSatoshis)))
commitInput, ShaChain.init, channelId = temporaryChannelId)
context.system.eventStream.publish(ChannelSignatureReceived(self, commitments))
goto(WAIT_FOR_FUNDING_CONFIRMED) using DATA_WAIT_FOR_FUNDING_CONFIRMED(temporaryChannelId, params, commitments, None, Left(fundingCreated))
goto(WAIT_FOR_FUNDING_CONFIRMED) using DATA_WAIT_FOR_FUNDING_CONFIRMED(temporaryChannelId, commitments, None, Left(fundingCreated))
}
case Event(CMD_CLOSE(_), _) => goto(CLOSED)
@ -295,22 +326,21 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
when(WAIT_FOR_FUNDING_CONFIRMED)(handleExceptions {
case Event(msg: FundingLocked, d: DATA_WAIT_FOR_FUNDING_CONFIRMED) =>
log.info(s"received their FundingLocked, deferring message")
stay using d.copy(deferred = Some(msg))
goto(stateName) using d.copy(deferred = Some(msg))
case Event(WatchEventConfirmed(BITCOIN_FUNDING_DEPTHOK, blockHeight, txIndex), DATA_WAIT_FOR_FUNDING_CONFIRMED(temporaryChannelId, params, commitments, deferred, lastSent)) =>
case Event(WatchEventConfirmed(BITCOIN_FUNDING_DEPTHOK, blockHeight, txIndex), DATA_WAIT_FOR_FUNDING_CONFIRMED(temporaryChannelId, commitments, deferred, lastSent)) =>
val channelId = toShortId(blockHeight, txIndex, commitments.commitInput.outPoint.index.toInt)
blockchain ! WatchLost(self, commitments.anchorId, params.minimumDepth, BITCOIN_FUNDING_LOST)
val nextPerCommitmentPoint = Generators.perCommitPoint(params.localParams.shaSeed, 1)
blockchain ! WatchLost(self, commitments.anchorId, nodeParams.minDepthBlocks, BITCOIN_FUNDING_LOST)
val nextPerCommitmentPoint = Generators.perCommitPoint(commitments.localParams.shaSeed, 1)
val fundingLocked = FundingLocked(temporaryChannelId, channelId, nextPerCommitmentPoint)
deferred.map(self ! _)
remote ! fundingLocked
log.info(s"unstashing messages")
// TODO: htlcIdx should not be 0 when resuming connection
goto(WAIT_FOR_FUNDING_LOCKED) using DATA_WAIT_FOR_FUNDING_LOCKED(params, commitments.copy(channelId = channelId), fundingLocked)
goto(WAIT_FOR_FUNDING_LOCKED) using DATA_WAIT_FOR_FUNDING_LOCKED(commitments.copy(channelId = channelId), fundingLocked)
// TODO: not implemented, maybe should be done with a state timer and not a blockchain watch?
case Event(BITCOIN_FUNDING_TIMEOUT, _) =>
remote ! Error(0, "Funding tx timed out".getBytes)
case Event(BITCOIN_FUNDING_TIMEOUT, d: DATA_WAIT_FOR_FUNDING_CONFIRMED) =>
remote ! Error(d.channelId, "Funding tx timed out".getBytes)
goto(CLOSED)
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_WAIT_FOR_FUNDING_CONFIRMED) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
@ -327,19 +357,20 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
// TODO: channel id mismatch, can happen if minDepth is to low, negotiation not suported yet
handleLocalError(new RuntimeException(s"channel id mismatch local=${d.channelId} remote=$remoteChannelId"), d)
case Event(FundingLocked(_, _, nextPerCommitmentPoint), d@DATA_WAIT_FOR_FUNDING_LOCKED(params, commitments, _)) =>
case Event(FundingLocked(_, _, nextPerCommitmentPoint), d@DATA_WAIT_FOR_FUNDING_LOCKED(commitments, _)) =>
log.info(s"channelId=${java.lang.Long.toUnsignedString(d.channelId)}")
Register.createAlias(remoteNodeId, d.channelId)
context.system.eventStream.publish(ChannelIdAssigned(self, d.channelId))
// this clock will be used to detect htlc timeouts
context.system.eventStream.subscribe(self, classOf[CurrentBlockCount])
if (Funding.announceChannel(params.localParams.localFeatures, params.remoteParams.localFeatures)) {
val (localNodeSig, localBitcoinSig) = Announcements.signChannelAnnouncement(d.channelId, nodeParams.privateKey, remoteNodeId, d.params.localParams.fundingPrivKey, d.params.remoteParams.fundingPubKey)
if (Funding.announceChannel(commitments.localParams.localFeatures, commitments.remoteParams.localFeatures)) {
val (localNodeSig, localBitcoinSig) = Announcements.signChannelAnnouncement(d.channelId, nodeParams.privateKey, remoteNodeId, commitments.localParams.fundingPrivKey, commitments.remoteParams.fundingPubKey)
val annSignatures = AnnouncementSignatures(d.channelId, localNodeSig, localBitcoinSig)
remote ! annSignatures
goto(WAIT_FOR_ANN_SIGNATURES) using DATA_WAIT_FOR_ANN_SIGNATURES(params, commitments.copy(remoteNextCommitInfo = Right(nextPerCommitmentPoint)), annSignatures)
// FD remote ! annSignatures
goto(WAIT_FOR_ANN_SIGNATURES) using DATA_WAIT_FOR_ANN_SIGNATURES(commitments.copy(remoteNextCommitInfo = Right(nextPerCommitmentPoint)).addToUnackedMessages(annSignatures), annSignatures)
} else {
log.info(s"channel ${d.channelId} won't be announced")
goto(NORMAL) using DATA_NORMAL(params, commitments.copy(remoteNextCommitInfo = Right(nextPerCommitmentPoint)), None)
goto(NORMAL) using DATA_NORMAL(commitments.copy(remoteNextCommitInfo = Right(nextPerCommitmentPoint)), None)
}
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_WAIT_FOR_FUNDING_LOCKED) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
@ -352,10 +383,10 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
})
when(WAIT_FOR_ANN_SIGNATURES)(handleExceptions {
case Event(AnnouncementSignatures(_, remoteNodeSig, remoteBitcoinSig), d@DATA_WAIT_FOR_ANN_SIGNATURES(params, commitments, _)) =>
case Event(AnnouncementSignatures(_, remoteNodeSig, remoteBitcoinSig), d@DATA_WAIT_FOR_ANN_SIGNATURES(commitments, _)) =>
log.info(s"announcing channel ${d.channelId} on the network")
val (localNodeSig, localBitcoinSig) = Announcements.signChannelAnnouncement(d.channelId, nodeParams.privateKey, remoteNodeId, d.params.localParams.fundingPrivKey, d.params.remoteParams.fundingPubKey)
val channelAnn = Announcements.makeChannelAnnouncement(d.channelId, nodeParams.privateKey.publicKey, remoteNodeId, d.params.localParams.fundingPrivKey.publicKey, d.params.remoteParams.fundingPubKey, localNodeSig, remoteNodeSig, localBitcoinSig, remoteBitcoinSig)
val (localNodeSig, localBitcoinSig) = Announcements.signChannelAnnouncement(d.channelId, nodeParams.privateKey, remoteNodeId, commitments.localParams.fundingPrivKey, commitments.remoteParams.fundingPubKey)
val channelAnn = Announcements.makeChannelAnnouncement(d.channelId, nodeParams.privateKey.publicKey, remoteNodeId, commitments.localParams.fundingPrivKey.publicKey, commitments.remoteParams.fundingPubKey, localNodeSig, remoteNodeSig, localBitcoinSig, remoteBitcoinSig)
val nodeAnn = Announcements.makeNodeAnnouncement(nodeParams.privateKey, nodeParams.alias, nodeParams.color, nodeParams.address :: Nil, Platform.currentTime / 1000)
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.privateKey, remoteNodeId, d.commitments.channelId, nodeParams.expiryDeltaBlocks, nodeParams.htlcMinimumMsat, nodeParams.feeBaseMsat, nodeParams.feeProportionalMillionth, Platform.currentTime / 1000)
router ! channelAnn
@ -364,7 +395,7 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
// let's trigger the broadcast immediately so that we don't wait for 60 seconds to announce our newly created channel
// we give 3 seconds for the router-watcher roundtrip
context.system.scheduler.scheduleOnce(3 seconds, router, 'tick_broadcast)
goto(NORMAL) using DATA_NORMAL(params, commitments, None)
goto(NORMAL) using DATA_NORMAL(commitments, None)
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_WAIT_FOR_ANN_SIGNATURES) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
@ -392,7 +423,7 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
case Event(c: CMD_ADD_HTLC, d: DATA_NORMAL) if d.unackedShutdown.isDefined =>
handleCommandError(sender, new RuntimeException("cannot send new htlcs, closing in progress"))
case Event(c@CMD_ADD_HTLC(amountMsat, rHash, expiry, route, downstream_opt, do_commit), d@DATA_NORMAL(params, commitments, _)) =>
case Event(c@CMD_ADD_HTLC(amountMsat, rHash, expiry, route, downstream_opt, do_commit), d@DATA_NORMAL(commitments, _)) =>
Try(Commitments.sendAdd(commitments, c)) match {
case Success((commitments1, add)) =>
val origin = downstream_opt.map(Relayed(_)).getOrElse(Local(sender))
@ -402,9 +433,9 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
case Failure(cause) => handleCommandError(sender, cause)
}
case Event(add: UpdateAddHtlc, d@DATA_NORMAL(params, commitments, _)) =>
case Event(add: UpdateAddHtlc, d@DATA_NORMAL(commitments, _)) =>
Try(Commitments.receiveAdd(commitments, add)) match {
case Success(commitments1) => stay using d.copy(commitments = commitments1)
case Success(commitments1) => goto(stateName) using d.copy(commitments = commitments1)
case Failure(cause) => handleLocalError(cause, d)
}
@ -416,12 +447,12 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
case Failure(cause) => handleCommandError(sender, cause)
}
case Event(fulfill@UpdateFulfillHtlc(_, id, r), d@DATA_NORMAL(params, commitments, _)) =>
case Event(fulfill@UpdateFulfillHtlc(_, id, r), d@DATA_NORMAL(commitments, _)) =>
Try(Commitments.receiveFulfill(d.commitments, fulfill)) match {
case Success(Right(commitments1)) =>
relayer ! ForwardFulfill(fulfill)
stay using d.copy(commitments = commitments1)
case Success(Left(_)) => stay
goto(stateName) using d.copy(commitments = commitments1)
case Success(Left(_)) => goto(stateName)
case Failure(cause) => handleLocalError(cause, d)
}
@ -433,12 +464,12 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
case Failure(cause) => handleCommandError(sender, cause)
}
case Event(fail@UpdateFailHtlc(_, id, reason), d@DATA_NORMAL(params, _, _)) =>
case Event(fail@UpdateFailHtlc(_, id, reason), d@DATA_NORMAL(_, _)) =>
Try(Commitments.receiveFail(d.commitments, fail)) match {
case Success(Right(commitments1)) =>
relayer ! ForwardFail(fail)
stay using d.copy(commitments = commitments1)
case Success(Left(_)) => stay
goto(stateName) using d.copy(commitments = commitments1)
case Success(Left(_)) => goto(stateName)
case Failure(cause) => handleLocalError(cause, d)
}
@ -446,7 +477,7 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
d.commitments.remoteNextCommitInfo match {
case _ if !Commitments.localHasChanges(d.commitments) =>
log.info("ignoring CMD_SIGN (nothing to sign)")
stay
goto(stateName)
case Right(_) =>
Try(Commitments.sendCommit(d.commitments)) match {
case Success((commitments1, commit)) =>
@ -456,23 +487,23 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
}
case Left(waitForRevocation) =>
log.debug(s"already in the process of signing, will sign again as soon as possible")
stay using d.copy(commitments = d.commitments.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true))))
goto(stateName) using d.copy(commitments = d.commitments.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true))))
}
case Event(commit@CommitSig(_, theirSig, theirHtlcSigs), d: DATA_NORMAL) =>
Try(Commitments.receiveCommit(d.commitments, commit)) match {
case Success(Right((commitments1, revocation))) =>
remote ! revocation
// FD remote ! revocation
log.debug(s"received a new sig, spec:\n${Commitments.specs2String(commitments1)}")
if (Commitments.localHasChanges(commitments1)) {
// if we have newly acknowledged changes let's sign them
self ! CMD_SIGN
}
context.system.eventStream.publish(ChannelSignatureReceived(self, commitments1))
stay using d.copy(commitments = commitments1)
goto(stateName) using d.copy(commitments = commitments1)
case Success(Left(_)) =>
// this was an old commit, nothing to do
stay
goto(stateName)
case Failure(cause) => handleLocalError(cause, d)
}
@ -492,10 +523,10 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
if (Commitments.localHasChanges(commitments1) && d.commitments.remoteNextCommitInfo.left.map(_.reSignAsap) == Left(true)) {
self ! CMD_SIGN
}
stay using d.copy(commitments = commitments1)
goto(stateName) using d.copy(commitments = commitments1)
case Success(Left(_)) =>
// this was an old revocation, nothing to do
stay
goto(stateName)
case Failure(cause) => handleLocalError(cause, d)
}
@ -507,37 +538,37 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
handleCommandError(sender, new RuntimeException("cannot close when there are pending changes"))
case Event(CMD_CLOSE(ourScriptPubKey_opt), d: DATA_NORMAL) =>
ourScriptPubKey_opt.getOrElse(d.params.localParams.defaultFinalScriptPubKey) match {
ourScriptPubKey_opt.getOrElse(d.commitments.localParams.defaultFinalScriptPubKey) match {
case finalScriptPubKey if Closing.isValidFinalScriptPubkey(finalScriptPubKey) =>
val localShutdown = Shutdown(d.channelId, finalScriptPubKey)
handleCommandSuccess(sender, localShutdown, d.copy(unackedShutdown = Some(localShutdown)))
handleCommandSuccess(sender, localShutdown, d.copy(unackedShutdown = Some(localShutdown), commitments = d.commitments.copy(unackedMessages = d.commitments.unackedMessages :+ localShutdown)))
case _ => handleCommandError(sender, new RuntimeException("invalid final script"))
}
case Event(remoteShutdown@Shutdown(_, remoteScriptPubKey), d@DATA_NORMAL(params, commitments, ourShutdownOpt)) if commitments.remoteChanges.proposed.size > 0 =>
case Event(remoteShutdown@Shutdown(_, remoteScriptPubKey), d@DATA_NORMAL(commitments, ourShutdownOpt)) if commitments.remoteChanges.proposed.size > 0 =>
handleLocalError(new RuntimeException("it is illegal to send a shutdown while having unsigned changes"), d)
case Event(remoteShutdown@Shutdown(_, remoteScriptPubKey), d@DATA_NORMAL(params, commitments, ourShutdownOpt)) =>
case Event(remoteShutdown@Shutdown(_, remoteScriptPubKey), d@DATA_NORMAL(commitments, ourShutdownOpt)) =>
Try(ourShutdownOpt.map(s => (s, commitments)).getOrElse {
require(Closing.isValidFinalScriptPubkey(remoteScriptPubKey), "invalid final script")
// first if we have pending changes, we need to commit them
val commitments2 = if (Commitments.localHasChanges(commitments)) {
val (commitments1, commit) = Commitments.sendCommit(d.commitments)
remote ! commit
// FD remote ! commit
commitments1
} else commitments
val shutdown = Shutdown(d.channelId, params.localParams.defaultFinalScriptPubKey)
remote ! shutdown
(shutdown, commitments2)
val shutdown = Shutdown(d.channelId, commitments.localParams.defaultFinalScriptPubKey)
// FD remote ! shutdown
(shutdown, commitments2.copy(unackedMessages = commitments2.unackedMessages :+ shutdown))
}) 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.nextRemoteCommit.spec.htlcs.size == 0) =>
val closingSigned = Closing.makeFirstClosingTx(params, commitments3, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey)
remote ! closingSigned
goto(NEGOTIATING) using DATA_NEGOTIATING(params, commitments3, localShutdown, remoteShutdown, closingSigned)
val closingSigned = Closing.makeFirstClosingTx(commitments3, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey)
// FD remote ! closingSigned
goto(NEGOTIATING) using DATA_NEGOTIATING(commitments3.copy(unackedMessages = commitments3.unackedMessages :+ closingSigned), localShutdown, remoteShutdown, closingSigned)
case Success((localShutdown, commitments3)) =>
goto(SHUTDOWN) using DATA_SHUTDOWN(params, commitments3, localShutdown, remoteShutdown)
goto(SHUTDOWN) using DATA_SHUTDOWN(commitments3, localShutdown, remoteShutdown)
case Failure(cause) => handleLocalError(cause, d)
}
@ -577,8 +608,8 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
Try(Commitments.receiveFulfill(d.commitments, fulfill)) match {
case Success(Right(commitments1)) =>
relayer ! ForwardFulfill(fulfill)
stay using d.copy(commitments = commitments1)
case Success(Left(_)) => stay
goto(stateName) using d.copy(commitments = commitments1)
case Success(Left(_)) => goto(stateName)
case Failure(cause) => handleLocalError(cause, d)
}
@ -592,8 +623,8 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
Try(Commitments.receiveFail(d.commitments, fail)) match {
case Success(Right(commitments1)) =>
relayer ! ForwardFail(fail)
stay using d.copy(commitments = commitments1)
case Success(Left(_)) => stay
goto(stateName) using d.copy(commitments = commitments1)
case Success(Left(_)) => goto(stateName)
case Failure(cause) => handleLocalError(cause, d)
}
@ -601,7 +632,7 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
d.commitments.remoteNextCommitInfo match {
case _ if !Commitments.localHasChanges(d.commitments) =>
log.info("ignoring CMD_SIGN (nothing to sign)")
stay
goto(stateName)
case Right(_) =>
Try(Commitments.sendCommit(d.commitments)) match {
case Success((commitments1, commit)) =>
@ -611,52 +642,52 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
}
case Left(waitForRevocation) =>
log.debug(s"already in the process of signing, will sign again as soon as possible")
stay using d.copy(commitments = d.commitments.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true))))
goto(stateName) using d.copy(commitments = d.commitments.copy(remoteNextCommitInfo = Left(waitForRevocation.copy(reSignAsap = true))))
}
case Event(msg@CommitSig(_, theirSig, theirHtlcSigs), d@DATA_SHUTDOWN(params, commitments, localShutdown, remoteShutdown)) =>
case Event(msg@CommitSig(_, theirSig, theirHtlcSigs), d@DATA_SHUTDOWN(commitments, localShutdown, remoteShutdown)) =>
// 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(Right((commitments1, revocation))) if commitments1.hasNoPendingHtlcs =>
remote ! revocation
val closingSigned = Closing.makeFirstClosingTx(params, commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey)
remote ! closingSigned
// FD remote ! revocation
val closingSigned = Closing.makeFirstClosingTx(commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey)
// FD remote ! closingSigned
log.debug(s"received a new sig, switching to NEGOTIATING spec:\n${Commitments.specs2String(commitments1)}")
goto(NEGOTIATING) using DATA_NEGOTIATING(params, commitments1, localShutdown, remoteShutdown, closingSigned)
goto(NEGOTIATING) using DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, closingSigned)
case Success(Right((commitments1, revocation))) =>
remote ! revocation
// FD remote ! revocation
if (Commitments.localHasChanges(commitments1)) {
// if we have newly acknowledged changes let's sign them
self ! CMD_SIGN
}
log.debug(s"received a new sig, spec:\n${Commitments.specs2String(commitments1)}")
context.system.eventStream.publish(ChannelSignatureReceived(self, commitments1))
stay using d.copy(commitments = commitments1)
goto(stateName) using d.copy(commitments = commitments1)
case Success(Left(_)) =>
// this was an old commit, nothing to do
stay
goto(stateName)
case Failure(cause) => handleLocalError(cause, d)
}
case Event(msg: RevokeAndAck, d@DATA_SHUTDOWN(params, commitments, localShutdown, remoteShutdown)) =>
case Event(msg: RevokeAndAck, d@DATA_SHUTDOWN(commitments, localShutdown, remoteShutdown)) =>
// we received a revocation because we sent a signature
// => all our changes have been acked
Try(Commitments.receiveRevocation(d.commitments, msg)) match {
case Success(Right(commitments1)) if commitments1.hasNoPendingHtlcs =>
val closingSigned = Closing.makeFirstClosingTx(params, commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey)
val closingSigned = Closing.makeFirstClosingTx(commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey)
remote ! closingSigned
// FD remote ! closingSigned
log.debug(s"received a new rev, switching to NEGOTIATING spec:\n${Commitments.specs2String(commitments1)}")
goto(NEGOTIATING) using DATA_NEGOTIATING(params, commitments1, localShutdown, remoteShutdown, closingSigned)
goto(NEGOTIATING) using DATA_NEGOTIATING(commitments1.copy(unackedMessages = commitments1.unackedMessages :+ closingSigned), localShutdown, remoteShutdown, closingSigned)
case Success(Right(commitments1)) =>
if (Commitments.localHasChanges(commitments1) && d.commitments.remoteNextCommitInfo.left.map(_.reSignAsap) == Left(true)) {
self ! CMD_SIGN
}
log.debug(s"received a new rev, spec:\n${Commitments.specs2String(commitments1)}")
stay using d.copy(commitments = commitments1)
goto(stateName) using d.copy(commitments = commitments1)
case Success(Left(_)) =>
// this was an old revocation, nothing to do
stay
goto(stateName)
case Failure(cause) => handleLocalError(cause, d)
}
@ -676,7 +707,7 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
when(NEGOTIATING)(handleExceptions {
case Event(ClosingSigned(_, remoteClosingFee, remoteSig), d: DATA_NEGOTIATING) if remoteClosingFee == d.localClosingSigned.feeSatoshis =>
Closing.checkClosingSignature(d.params, d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, Satoshi(remoteClosingFee), remoteSig) match {
Closing.checkClosingSignature(d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, Satoshi(remoteClosingFee), remoteSig) match {
case Success(signedClosingTx) =>
publishMutualClosing(signedClosingTx)
goto(CLOSING) using DATA_CLOSING(d.commitments, ourSignature = Some(d.localClosingSigned), mutualClosePublished = Some(signedClosingTx))
@ -686,25 +717,25 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
}
case Event(ClosingSigned(_, remoteClosingFee, remoteSig), d: DATA_NEGOTIATING) =>
Closing.checkClosingSignature(d.params, d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, Satoshi(remoteClosingFee), remoteSig) match {
Closing.checkClosingSignature(d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, Satoshi(remoteClosingFee), remoteSig) match {
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)
val (_, closingSigned) = Closing.makeClosingTx(d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nextClosingFee)
remote ! closingSigned
if (nextClosingFee == Satoshi(remoteClosingFee)) {
publishMutualClosing(signedClosingTx)
goto(CLOSING) using DATA_CLOSING(d.commitments, ourSignature = Some(closingSigned), mutualClosePublished = Some(signedClosingTx))
} else {
stay using d.copy(localClosingSigned = closingSigned)
goto(stateName) using d.copy(localClosingSigned = closingSigned)
}
case Failure(cause) =>
log.error(cause, "cannot verify their close signature")
throw new RuntimeException("cannot verify their close signature", cause)
}
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_NEGOTIATING) if tx.txid == Closing.makeClosingTx(d.params, d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, Satoshi(d.localClosingSigned.feeSatoshis))._1.tx.txid =>
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_NEGOTIATING) if tx.txid == Closing.makeClosingTx(d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, Satoshi(d.localClosingSigned.feeSatoshis))._1.tx.txid =>
// happens when we agreed on a closeSig, but we don't know it yet: we receive the watcher notification before their ClosingSigned (which will match ours)
stay()
goto(stateName)
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_NEGOTIATING) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
@ -720,11 +751,11 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_CLOSING) if tx.txid == d.commitments.localCommit.publishableTxs.commitTx.tx.txid =>
// we just initiated a uniclose moments ago and are now receiving the blockchain notification, there is nothing to do
stay()
goto(stateName)
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_CLOSING) if Some(tx.txid) == d.mutualClosePublished.map(_.txid) =>
// we just published a mutual close tx, we are notified but it's alright
stay()
goto(stateName)
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_CLOSING) if tx.txid == d.commitments.remoteCommit.txid =>
// counterparty may attempt to spend its last commit tx at any time
@ -744,7 +775,7 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
case Event(WatchEventConfirmed(BITCOIN_PENALTY_DONE, _, _), d: DATA_CLOSING) if d.revokedCommitPublished.size > 0 => goto(CLOSED)
case Event(e: Error, d: DATA_CLOSING) => stay // nothing to do, there is already a spending tx published
case Event(e: Error, d: DATA_CLOSING) => goto(stateName) // nothing to do, there is already a spending tx published
}
when(CLOSED, stateTimeout = 10 seconds) {
@ -763,13 +794,13 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
remote ! d.lastSent
goto(WAIT_FOR_ACCEPT_CHANNEL)
case Event(INPUT_RECONNECTED(r), d@DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId, params, pushMsat, _, lastSent)) =>
case Event(INPUT_RECONNECTED(r), d@DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, _, lastSent)) =>
remote = r
remote ! d.lastSent
// in this particular case we need to go to previous state because of the internal funding request to our wallet
// let's rebuild the previous state data
val remoteInit = Init(params.remoteParams.globalFeatures, params.remoteParams.localFeatures)
val initFunder = INPUT_INIT_FUNDER(remoteNodeId, temporaryChannelId, params.fundingSatoshis, pushMsat, params.localParams, remoteInit)
val remoteInit = Init(remoteParams.globalFeatures, remoteParams.localFeatures)
val initFunder = INPUT_INIT_FUNDER(remoteNodeId, temporaryChannelId, fundingSatoshis, pushMsat, localParams, remoteInit)
goto(WAIT_FOR_ACCEPT_CHANNEL) using DATA_WAIT_FOR_ACCEPT_CHANNEL(initFunder, lastSent)
case Event(INPUT_RECONNECTED(r), d: DATA_WAIT_FOR_FUNDING_CREATED) =>
@ -782,14 +813,16 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
remote ! d.lastSent
goto(WAIT_FOR_FUNDING_SIGNED)
case Event(INPUT_RECONNECTED(r), DATA_WAIT_FOR_FUNDING_CONFIRMED(_, _, _, _, Left(fundingCreated))) =>
case Event(INPUT_RECONNECTED(r), DATA_WAIT_FOR_FUNDING_CONFIRMED(_, commitments, _, Left(fundingCreated))) =>
remote = r
remote ! fundingCreated
blockchain ! WatchConfirmed(self, commitments.commitInput.outPoint.txid, nodeParams.minDepthBlocks, BITCOIN_FUNDING_DEPTHOK)
goto(WAIT_FOR_FUNDING_CONFIRMED)
case Event(INPUT_RECONNECTED(r), DATA_WAIT_FOR_FUNDING_CONFIRMED(_, _, _, _, Right(fundingSigned))) =>
case Event(INPUT_RECONNECTED(r), DATA_WAIT_FOR_FUNDING_CONFIRMED(_, commitments, _, Right(fundingSigned))) =>
remote = r
remote ! fundingSigned
blockchain ! WatchConfirmed(self, commitments.commitInput.outPoint.txid, nodeParams.minDepthBlocks, BITCOIN_FUNDING_DEPTHOK)
goto(WAIT_FOR_FUNDING_CONFIRMED)
case Event(INPUT_RECONNECTED(r), d: DATA_WAIT_FOR_FUNDING_LOCKED) =>
@ -805,8 +838,8 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
case Event(INPUT_RECONNECTED(r), d: DATA_NORMAL) if d.commitments.localCommit.index == 0 && d.commitments.remoteCommit.index == 0 && d.commitments.remoteChanges.proposed.size == 0 && d.commitments.remoteNextCommitInfo.isRight =>
remote = r
// this is a brand new channel
if (Funding.announceChannel(d.params.localParams.localFeatures, d.params.remoteParams.localFeatures)) {
val (localNodeSig, localBitcoinSig) = Announcements.signChannelAnnouncement(d.channelId, nodeParams.privateKey, remoteNodeId, d.params.localParams.fundingPrivKey, d.params.remoteParams.fundingPubKey)
if (Funding.announceChannel(d.commitments.localParams.localFeatures, d.commitments.remoteParams.localFeatures)) {
val (localNodeSig, localBitcoinSig) = Announcements.signChannelAnnouncement(d.channelId, nodeParams.privateKey, remoteNodeId, d.commitments.localParams.fundingPrivKey, d.commitments.remoteParams.fundingPubKey)
val annSignatures = AnnouncementSignatures(d.channelId, localNodeSig, localBitcoinSig)
remote ! annSignatures
} else {
@ -814,7 +847,7 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
}
goto(NORMAL)
case Event(INPUT_RECONNECTED(r), d@DATA_NORMAL(_, commitments, _)) =>
case Event(INPUT_RECONNECTED(r), d@DATA_NORMAL(commitments, _)) =>
remote = r
log.info(s"resuming with ${Commitments.changes2String(commitments)}")
//val resend = commitments.unackedMessages.filterNot(_.isInstanceOf[RevokeAndAck])
@ -827,14 +860,20 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
}
goto(NORMAL)
case Event(c@CMD_ADD_HTLC(amountMsat, rHash, expiry, route, downstream_opt, do_commit), d@DATA_NORMAL(params, commitments, _)) =>
case Event(INPUT_RECONNECTED(r), d: DATA_NEGOTIATING) =>
goto(NEGOTIATING) using (d)
case Event(INPUT_RECONNECTED(r), d: DATA_CLOSING) =>
goto(CLOSING) using (d)
case Event(c@CMD_ADD_HTLC(amountMsat, rHash, expiry, route, downstream_opt, do_commit), d@DATA_NORMAL(commitments, _)) =>
log.info(s"we are disconnected so we just include the add in our commitments")
Try(Commitments.sendAdd(commitments, c)) match {
case Success((commitments1, add)) =>
val origin = downstream_opt.map(Relayed(_)).getOrElse(Local(sender))
relayer ! Bind(add, origin)
sender ! "ok"
stay using d.copy(commitments = commitments1)
goto(stateName) using d.copy(commitments = commitments1)
case Failure(cause) => handleCommandError(sender, cause)
}
@ -843,7 +882,7 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
Try(Commitments.sendFulfill(d.commitments, c)) match {
case Success((commitments1, fulfill)) =>
sender ! "ok"
stay using d.copy(commitments = commitments1)
goto(stateName) using d.copy(commitments = commitments1)
case Failure(cause) => handleCommandError(sender, cause)
}
}
@ -864,11 +903,11 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
case Event(CMD_GETSTATE, _) =>
sender ! stateName
stay
goto(stateName)
case Event(CMD_GETSTATEDATA, _) =>
sender ! stateData
stay
goto(stateName)
case Event(CMD_GETINFO, _) =>
sender ! RES_GETINFO(remoteNodeId, stateData match {
@ -883,14 +922,38 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
case c: DATA_CLOSING => 0L
case _ => 0L
}, stateName, stateData)
stay
goto(stateName)
// we only care about this event in NORMAL and SHUTDOWN state, and we never unregister to the event stream
case Event(CurrentBlockCount(_), _) => stay
case Event(CurrentBlockCount(_), _) => goto(stateName)
}
onTransition {
case previousState -> currentState => context.system.eventStream.publish(ChannelChangedState(self, context.parent, remoteNodeId, previousState, currentState, nextStateData))
case previousState -> currentState =>
if (currentState != previousState) {
context.system.eventStream.publish(ChannelStateChanged(self, context.parent, remoteNodeId, previousState, currentState, nextStateData))
}
(stateData, nextStateData) match {
case (from: HasCommitments, to: HasCommitments) =>
// use the the transition callback to store state and send messages
val nextChannelId = to.channelId
val toSend = if (remote == null) {
Seq()
} else {
// we only send newly added unacked messages
val nextMessages = to.commitments.unackedMessages
val currentMessages = from.commitments.unackedMessages
nextMessages.filterNot(c => currentMessages.contains(c))
}
forwarder ! StoreAndForward(toSend, remote, nextChannelId, ChannelState(remoteNodeId, currentState, nextStateData))
case (_, to: HasCommitments) =>
val nextChannelId = to.channelId
forwarder ! StoreAndForward(Nil, remote, nextChannelId, ChannelState(remoteNodeId, currentState, nextStateData))
case _ => ()
}
}
/*
@ -905,22 +968,22 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
*/
def handleCommandSuccess(sender: ActorRef, msg: LightningMessage, newData: Data) = {
remote ! msg
// FD remote ! msg
if (sender != self) {
sender ! "ok"
}
stay using newData
goto(stateName) using newData
}
def handleCommandError(sender: ActorRef, cause: Throwable) = {
log.error(cause, "")
sender ! cause.getMessage
stay
goto(stateName)
}
def handleLocalError(cause: Throwable, d: HasCommitments) = {
log.error(cause, "")
remote ! Error(0, cause.getMessage.getBytes)
remote ! Error(d.channelId, cause.getMessage.getBytes)
spendLocalCurrent(d)
}
@ -1038,7 +1101,7 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
Helpers.Closing.claimRevokedRemoteCommitTxOutputs(d.commitments, tx) match {
case Some(revokedCommitPublished) =>
log.warning(s"txid=${tx.txid} was a revoked commitment, publishing the penalty tx")
remote ! Error(0, "Funding tx has been spent".getBytes)
remote ! Error(d.channelId, "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)
@ -1068,8 +1131,7 @@ class Channel(nodeParams: NodeParams, val r: ActorRef, val blockchain: ActorRef,
log.error(s"our funding tx ${
d.commitments.anchorId
} was spent !!")
// TODO! channel id
remote ! Error(0, "Funding tx has been spent".getBytes)
remote ! Error(d.channelId, "Funding tx has been spent".getBytes)
// TODO: not enough
val commitTx = d.commitments.localCommit.publishableTxs.commitTx.tx
blockchain ! PublishAsap(commitTx)

View file

@ -10,10 +10,12 @@ import fr.acinq.bitcoin.{BinaryData, Satoshi}
trait ChannelEvent
case class ChannelCreated(temporaryChannelId: Long, peer: ActorRef, channel: ActorRef, params: LocalParams, remoteNodeId: PublicKey) extends ChannelEvent
case class ChannelCreated(channel: ActorRef, peer: ActorRef, remoteNodeId: PublicKey, isFunder: Boolean, temporaryChannelId: Long) extends ChannelEvent
case class ChannelIdAssigned(channel: ActorRef, channelId: BinaryData, amount: Satoshi) extends ChannelEvent
case class ChannelRestored(channel: ActorRef, peer: ActorRef, remoteNodeId: PublicKey, isFunder: Boolean, channelId: Long, currentData: Data) extends ChannelEvent
case class ChannelChangedState(channel: ActorRef, transport: ActorRef, remoteNodeId: PublicKey, previousState: State, currentState: State, currentData: Data) extends ChannelEvent
case class ChannelIdAssigned(channel: ActorRef, channelId: Long) extends ChannelEvent
case class ChannelStateChanged(channel: ActorRef, peer: ActorRef, remoteNodeId: PublicKey, previousState: State, currentState: State, currentData: Data) extends ChannelEvent
case class ChannelSignatureReceived(channel: ActorRef, Commitments: Commitments) extends ChannelEvent

View file

@ -2,8 +2,8 @@ package fr.acinq.eclair.channel
import akka.actor.ActorRef
import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, PublicKey, Scalar}
import fr.acinq.bitcoin.{BinaryData, ScriptElt, Transaction}
import fr.acinq.eclair.payment.{Local, Origin, Relayed}
import fr.acinq.bitcoin.{BinaryData, Transaction}
import fr.acinq.eclair.db.ChannelState
import fr.acinq.eclair.transactions.CommitmentSpec
import fr.acinq.eclair.transactions.Transactions.CommitTx
import fr.acinq.eclair.wire.{AcceptChannel, AnnouncementSignatures, ClosingSigned, FundingCreated, FundingLocked, FundingSigned, Init, OpenChannel, Shutdown, UpdateAddHtlc}
@ -63,6 +63,7 @@ case object INPUT_NO_MORE_HTLCS
case object INPUT_CLOSE_COMPLETE_TIMEOUT
case object INPUT_DISCONNECTED
case class INPUT_RECONNECTED(remote: ActorRef)
case class INPUT_RESTORED(channelId: Long, channelstate: ChannelState)
sealed trait BitcoinEvent
case object BITCOIN_FUNDING_DEPTHOK extends BitcoinEvent
@ -126,16 +127,16 @@ case class RevokedCommitPublished(commitTx: Transaction, claimMainOutputTx: Opti
final case class DATA_WAIT_FOR_OPEN_CHANNEL(initFundee: INPUT_INIT_FUNDEE) extends Data
final case class DATA_WAIT_FOR_ACCEPT_CHANNEL(initFunder: INPUT_INIT_FUNDER, lastSent: OpenChannel) extends Data
final case class DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId: Long, params: ChannelParams, pushMsat: Long, remoteFirstPerCommitmentPoint: Point, lastSent: OpenChannel) extends Data
final case class DATA_WAIT_FOR_FUNDING_CREATED(temporaryChannelId: Long, params: ChannelParams, pushMsat: Long, remoteFirstPerCommitmentPoint: Point, lastSent: AcceptChannel) extends Data
final case class DATA_WAIT_FOR_FUNDING_SIGNED(temporaryChannelId: Long, params: ChannelParams, fundingTx: Transaction, localSpec: CommitmentSpec, localCommitTx: CommitTx, remoteCommit: RemoteCommit, lastSent: FundingCreated) extends Data
final case class DATA_WAIT_FOR_FUNDING_CONFIRMED(temporaryChannelId: Long, params: ChannelParams, commitments: Commitments, deferred: Option[FundingLocked], lastSent: Either[FundingCreated, FundingSigned]) extends Data with HasCommitments
final case class DATA_WAIT_FOR_FUNDING_LOCKED(params: ChannelParams, commitments: Commitments, lastSent: FundingLocked) extends Data with HasCommitments
final case class DATA_WAIT_FOR_ANN_SIGNATURES(params: ChannelParams, commitments: Commitments, lastSent: AnnouncementSignatures) extends Data with HasCommitments
final case class DATA_NORMAL(params: ChannelParams, commitments: Commitments, unackedShutdown: Option[Shutdown]) extends Data with HasCommitments
final case class DATA_SHUTDOWN(params: ChannelParams, commitments: Commitments,
final case class DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId: Long, localParams: LocalParams, remoteParams: RemoteParams, fundingSatoshis: Long, pushMsat: Long, remoteFirstPerCommitmentPoint: Point, lastSent: OpenChannel) extends Data
final case class DATA_WAIT_FOR_FUNDING_CREATED(temporaryChannelId: Long, localParams: LocalParams, remoteParams: RemoteParams, fundingSatoshis: Long, pushMsat: Long, remoteFirstPerCommitmentPoint: Point, lastSent: AcceptChannel) extends Data
final case class DATA_WAIT_FOR_FUNDING_SIGNED(temporaryChannelId: Long, localParams: LocalParams, remoteParams: RemoteParams, fundingTx: Transaction, localSpec: CommitmentSpec, localCommitTx: CommitTx, remoteCommit: RemoteCommit, lastSent: FundingCreated) extends Data
final case class DATA_WAIT_FOR_FUNDING_CONFIRMED(temporaryChannelId: Long, commitments: Commitments, deferred: Option[FundingLocked], lastSent: Either[FundingCreated, FundingSigned]) extends Data with HasCommitments
final case class DATA_WAIT_FOR_FUNDING_LOCKED(commitments: Commitments, lastSent: FundingLocked) extends Data with HasCommitments
final case class DATA_WAIT_FOR_ANN_SIGNATURES(commitments: Commitments, lastSent: AnnouncementSignatures) extends Data with HasCommitments
final case class DATA_NORMAL(commitments: Commitments, unackedShutdown: Option[Shutdown]) extends Data with HasCommitments
final case class DATA_SHUTDOWN(commitments: Commitments,
localShutdown: Shutdown, remoteShutdown: Shutdown) extends Data with HasCommitments
final case class DATA_NEGOTIATING(params: ChannelParams, commitments: Commitments,
final case class DATA_NEGOTIATING(commitments: Commitments,
localShutdown: Shutdown, remoteShutdown: Shutdown, localClosingSigned: ClosingSigned) extends Data with HasCommitments
final case class DATA_CLOSING(commitments: Commitments,
ourSignature: Option[ClosingSigned] = None,
@ -147,11 +148,6 @@ final case class DATA_CLOSING(commitments: Commitments,
require(mutualClosePublished.isDefined || localCommitPublished.isDefined || remoteCommitPublished.isDefined || nextRemoteCommitPublished.isDefined || revokedCommitPublished.size > 0, "there should be at least one tx published in this state")
}
final case class ChannelParams(localParams: LocalParams,
remoteParams: RemoteParams,
fundingSatoshis: Long,
minimumDepth: Long)
final case class LocalParams(dustLimitSatoshis: Long,
maxHtlcValueInFlightMsat: Long,
channelReserveSatoshis: Long,

View file

@ -49,6 +49,8 @@ case class Commitments(localParams: LocalParams, remoteParams: RemoteParams,
def addLocalProposal(proposal: UpdateMessage): Commitments = Commitments.addLocalProposal(this, proposal)
def addRemoteProposal(proposal: UpdateMessage): Commitments = Commitments.addRemoteProposal(this, proposal)
def addToUnackedMessages(message: LightningMessage) : Commitments = this.copy(unackedMessages = unackedMessages :+ message)
}
object Commitments extends Logging {
@ -427,6 +429,7 @@ object Commitments extends Logging {
case _: CommitSig => s"sig"
case _: RevokeAndAck => s"rev"
case _: Error => s"err"
case _: FundingLocked => s"funding_locked"
case _ => "???"
}

View file

@ -0,0 +1,40 @@
package fr.acinq.eclair.channel
import akka.actor.{Actor, ActorLogging, ActorRef}
import fr.acinq.eclair.NodeParams
import fr.acinq.eclair.db.ChannelState
import fr.acinq.eclair.wire.LightningMessage
/**
* Created by fabrice on 27/02/17.
*/
case class StoreAndForward(messages: Seq[LightningMessage], destination: ActorRef, channelId: Long, channelState: ChannelState)
object StoreAndForward {
def apply(message: LightningMessage, destination: ActorRef, channelId: Long, channelState: ChannelState) = new StoreAndForward(Seq(message), destination, channelId, channelState)
}
class Forwarder(nodeParams: NodeParams) extends Actor with ActorLogging {
val db = nodeParams.db
val channelDb = Channel.makeChannelDb(db)
def receive = {
case StoreAndForward(messages, destination, channelId, channelState) =>
channelDb.put(channelId, ChannelRecord(channelId, channelState))
messages.foreach(message => destination forward message)
context become main(channelId)
}
def main(currentChannelId: Long): Receive = {
case StoreAndForward(messages, destination, channelId, channelState) if channelId != currentChannelId =>
log.info(s"channel changed id: $currentChannelId -> $channelId")
channelDb.put(channelId, ChannelRecord(channelId, channelState))
db.delete(currentChannelId.toString)
messages.foreach(message => destination forward message)
context become main(channelId)
case StoreAndForward(messages, destination, channelId, channelState) =>
channelDb.put(channelId, ChannelRecord(channelId, channelState))
messages.foreach(message => destination forward message)
}
}

View file

@ -4,7 +4,7 @@ import fr.acinq.bitcoin.Crypto.{Point, PublicKey, Scalar, sha256}
import fr.acinq.bitcoin.Script._
import fr.acinq.bitcoin.{OutPoint, _}
import fr.acinq.eclair.Features.Unset
import fr.acinq.eclair.{Features, Globals, NodeParams}
import fr.acinq.eclair.{Features, NodeParams}
import fr.acinq.eclair.crypto.Generators
import fr.acinq.eclair.transactions.Scripts._
import fr.acinq.eclair.transactions.Transactions._
@ -20,12 +20,37 @@ import scala.util.{Failure, Success, Try}
object Helpers {
object Funding {
/**
* Depending on the state, returns the current temporaryChannelId or channelId
* @param stateData
* @return
*/
def getChannelId(stateData: Data): Long = stateData match {
case Nothing => ???
case d: DATA_WAIT_FOR_OPEN_CHANNEL => d.initFundee.temporaryChannelId
case d: DATA_WAIT_FOR_ACCEPT_CHANNEL => d.initFunder.temporaryChannelId
case d: DATA_WAIT_FOR_FUNDING_INTERNAL => d.temporaryChannelId
case d: DATA_WAIT_FOR_FUNDING_CREATED => d.temporaryChannelId
case d: DATA_WAIT_FOR_FUNDING_SIGNED => d.temporaryChannelId
case d: HasCommitments => d.channelId
}
def validateParams(nodeParams: NodeParams, channelReserveSatoshis: Long, fundingSatoshis: Long): Unit = {
val reserveToFundingRatio = channelReserveSatoshis.toDouble / fundingSatoshis
require(reserveToFundingRatio <= nodeParams.maxReserveToFundingRatio, s"channelReserveSatoshis too high: ratio=$reserveToFundingRatio max=${nodeParams.maxReserveToFundingRatio}")
}
def getLocalParams(stateData: Data): LocalParams = stateData match {
case Nothing => ???
case d: DATA_WAIT_FOR_OPEN_CHANNEL => d.initFundee.localParams
case d: DATA_WAIT_FOR_ACCEPT_CHANNEL => d.initFunder.localParams
case d: DATA_WAIT_FOR_FUNDING_INTERNAL => d.localParams
case d: DATA_WAIT_FOR_FUNDING_CREATED => d.localParams
case d: DATA_WAIT_FOR_FUNDING_SIGNED => d.localParams
case d: HasCommitments => d.commitments.localParams
}
def validateParams(nodeParams: NodeParams, channelReserveSatoshis: Long, fundingSatoshis: Long): Unit = {
val reserveToFundingRatio = channelReserveSatoshis.toDouble / fundingSatoshis
require(reserveToFundingRatio <= nodeParams.maxReserveToFundingRatio, s"channelReserveSatoshis too high: ratio=$reserveToFundingRatio max=${nodeParams.maxReserveToFundingRatio}")
}
object Funding {
def makeFundingInputInfo(fundingTxId: BinaryData, fundingTxOutputIndex: Int, fundingSatoshis: Satoshi, fundingPubkey1: PublicKey, fundingPubkey2: PublicKey): InputInfo = {
val fundingScript = multiSig2of2(fundingPubkey1, fundingPubkey2)
@ -36,25 +61,26 @@ object Helpers {
/**
* Creates both sides's first commitment transaction
*
* @param params
* @param localParams
* @param remoteParams
* @param pushMsat
* @param fundingTxHash
* @param fundingTxOutputIndex
* @param remoteFirstPerCommitmentPoint
* @return (localSpec, localTx, remoteSpec, remoteTx, fundingTxOutput)
*/
def makeFirstCommitTxs(params: ChannelParams, pushMsat: Long, fundingTxHash: BinaryData, fundingTxOutputIndex: Int, remoteFirstPerCommitmentPoint: Point): (CommitmentSpec, CommitTx, CommitmentSpec, CommitTx) = {
val toLocalMsat = if (params.localParams.isFunder) params.fundingSatoshis * 1000 - pushMsat else pushMsat
val toRemoteMsat = if (params.localParams.isFunder) pushMsat else params.fundingSatoshis * 1000 - pushMsat
def makeFirstCommitTxs(localParams: LocalParams, remoteParams: RemoteParams, fundingSatoshis: Long, pushMsat: Long, fundingTxHash: BinaryData, fundingTxOutputIndex: Int, remoteFirstPerCommitmentPoint: Point): (CommitmentSpec, CommitTx, CommitmentSpec, CommitTx) = {
val toLocalMsat = if (localParams.isFunder) fundingSatoshis * 1000 - pushMsat else pushMsat
val toRemoteMsat = if (localParams.isFunder) pushMsat else fundingSatoshis * 1000 - pushMsat
// local and remote feerate are the same at this point (funder gets to choose the initial feerate)
val localSpec = CommitmentSpec(Set.empty[Htlc], feeRatePerKw = params.localParams.feeratePerKw, toLocalMsat = toLocalMsat, toRemoteMsat = toRemoteMsat)
val remoteSpec = CommitmentSpec(Set.empty[Htlc], feeRatePerKw = params.remoteParams.feeratePerKw, toLocalMsat = toRemoteMsat, toRemoteMsat = toLocalMsat)
val localSpec = CommitmentSpec(Set.empty[Htlc], feeRatePerKw = localParams.feeratePerKw, toLocalMsat = toLocalMsat, toRemoteMsat = toRemoteMsat)
val remoteSpec = CommitmentSpec(Set.empty[Htlc], feeRatePerKw = remoteParams.feeratePerKw, toLocalMsat = toRemoteMsat, toRemoteMsat = toLocalMsat)
val commitmentInput = makeFundingInputInfo(fundingTxHash, fundingTxOutputIndex, Satoshi(params.fundingSatoshis), params.localParams.fundingPrivKey.publicKey, params.remoteParams.fundingPubKey)
val localPerCommitmentPoint = Generators.perCommitPoint(params.localParams.shaSeed, 0)
val (localCommitTx, _, _) = Commitments.makeLocalTxs(0, params.localParams, params.remoteParams, commitmentInput, localPerCommitmentPoint, localSpec)
val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(0, params.localParams, params.remoteParams, commitmentInput, remoteFirstPerCommitmentPoint, remoteSpec)
val commitmentInput = makeFundingInputInfo(fundingTxHash, fundingTxOutputIndex, Satoshi(fundingSatoshis), localParams.fundingPrivKey.publicKey, remoteParams.fundingPubKey)
val localPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, 0)
val (localCommitTx, _, _) = Commitments.makeLocalTxs(0, localParams, remoteParams, commitmentInput, localPerCommitmentPoint, localSpec)
val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(0, localParams, remoteParams, commitmentInput, remoteFirstPerCommitmentPoint, remoteSpec)
(localSpec, localCommitTx, remoteSpec, remoteCommitTx)
}
@ -79,34 +105,34 @@ object Helpers {
}
}
def makeFirstClosingTx(params: ChannelParams, commitments: Commitments, localScriptPubkey: BinaryData, remoteScriptPubkey: BinaryData): ClosingSigned = {
def makeFirstClosingTx(commitments: Commitments, localScriptPubkey: BinaryData, remoteScriptPubkey: BinaryData): ClosingSigned = {
logger.info(s"making first closing tx with commitments:\n${Commitments.specs2String(commitments)}")
import commitments._
val closingFee = {
// this is just to estimate the weight
val dummyClosingTx = Transactions.makeClosingTx(commitInput, localScriptPubkey, remoteScriptPubkey, localParams.isFunder, Satoshi(0), Satoshi(0), localCommit.spec)
val closingWeight = Transaction.weight(Transactions.addSigs(dummyClosingTx, localParams.fundingPrivKey.publicKey, remoteParams.fundingPubKey, "aa" * 71, "bb" * 71).tx)
Transactions.weight2fee(params.localParams.feeratePerKw, closingWeight)
Transactions.weight2fee(commitments.localParams.feeratePerKw, closingWeight)
}
val (_, closingSigned) = makeClosingTx(params, commitments, localScriptPubkey, remoteScriptPubkey, closingFee)
val (_, closingSigned) = makeClosingTx(commitments, localScriptPubkey, remoteScriptPubkey, closingFee)
closingSigned
}
def makeClosingTx(params: ChannelParams, commitments: Commitments, localScriptPubkey: BinaryData, remoteScriptPubkey: BinaryData, closingFee: Satoshi): (ClosingTx, ClosingSigned) = {
def makeClosingTx(commitments: Commitments, localScriptPubkey: BinaryData, remoteScriptPubkey: BinaryData, closingFee: Satoshi): (ClosingTx, ClosingSigned) = {
import commitments._
require(isValidFinalScriptPubkey(localScriptPubkey), "invalid localScriptPubkey")
require(isValidFinalScriptPubkey(remoteScriptPubkey), "invalid remoteScriptPubkey")
// TODO: check that
val dustLimitSatoshis = Satoshi(Math.max(localParams.dustLimitSatoshis, remoteParams.dustLimitSatoshis))
val closingTx = Transactions.makeClosingTx(commitInput, localScriptPubkey, remoteScriptPubkey, localParams.isFunder, dustLimitSatoshis, closingFee, localCommit.spec)
val localClosingSig = Transactions.sign(closingTx, params.localParams.fundingPrivKey)
val localClosingSig = Transactions.sign(closingTx, commitments.localParams.fundingPrivKey)
val closingSigned = ClosingSigned(channelId, closingFee.amount, localClosingSig)
(closingTx, closingSigned)
}
def checkClosingSignature(params: ChannelParams, commitments: Commitments, localScriptPubkey: BinaryData, remoteScriptPubkey: BinaryData, closingFee: Satoshi, remoteClosingSig: BinaryData): Try[Transaction] = {
def checkClosingSignature(commitments: Commitments, localScriptPubkey: BinaryData, remoteScriptPubkey: BinaryData, closingFee: Satoshi, remoteClosingSig: BinaryData): Try[Transaction] = {
import commitments._
val (closingTx, closingSigned) = makeClosingTx(params, commitments, localScriptPubkey, remoteScriptPubkey, closingFee)
val (closingTx, closingSigned) = makeClosingTx(commitments, localScriptPubkey, remoteScriptPubkey, closingFee)
val signedClosingTx = Transactions.addSigs(closingTx, localParams.fundingPrivKey.publicKey, remoteParams.fundingPubKey, closingSigned.signature, remoteClosingSig)
Transactions.checkSpendable(signedClosingTx).map(x => signedClosingTx.tx)
}
@ -202,7 +228,7 @@ object Helpers {
* @return a list of transactions (one per HTLC that we can claim)
*/
def claimRemoteCommitTxOutputs(commitments: Commitments, remoteCommit: RemoteCommit, tx: Transaction): RemoteCommitPublished = {
import commitments.{localParams, remoteParams, commitInput}
import commitments.{commitInput, localParams, remoteParams}
require(remoteCommit.txid == tx.txid, "txid mismatch, provided tx is not the current remote commit tx")
val (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = Commitments.makeRemoteTxs(remoteCommit.index, localParams, remoteParams, commitInput, remoteCommit.remotePerCommitmentPoint, remoteCommit.spec)
require(remoteCommitTx.tx.txid == tx.txid, "txid mismatch, cannot recompute the current remote commit tx")

View file

@ -26,17 +26,20 @@ import fr.acinq.bitcoin.Crypto.PublicKey
class Register extends Actor with ActorLogging {
context.system.eventStream.subscribe(self, classOf[ChannelCreated])
context.system.eventStream.subscribe(self, classOf[ChannelChangedState])
context.system.eventStream.subscribe(self, classOf[ChannelStateChanged])
override def receive: Receive = main(Map())
def main(channels: Map[String, ActorRef]): Receive = {
case ChannelCreated(temporaryChannelId, _, channel, _, _) =>
case ChannelCreated(channel, _, _, _, temporaryChannelId) =>
context.watch(channel)
context become main(channels + (java.lang.Long.toHexString(temporaryChannelId) -> channel))
case ChannelChangedState(channel, _, _, _, _, d: DATA_WAIT_FOR_FUNDING_LOCKED) =>
case ChannelRestored(channel, _, _, _, channelId, _) =>
context.watch(channel)
context become main(channels + (java.lang.Long.toHexString(channelId) -> channel))
case ChannelStateChanged(channel, _, _, _, _, d: DATA_WAIT_FOR_FUNDING_LOCKED) =>
import d.commitments.channelId
context.watch(channel)
// channel id switch

View file

@ -1,6 +1,8 @@
package fr.acinq.eclair.crypto
import fr.acinq.bitcoin._
import fr.acinq.eclair.wire.Codecs
import scodec.Codec
import scala.annotation.tailrec
@ -20,7 +22,7 @@ object ShaChain {
* @param index 64-bit integer
* @return a binary representation of index as a sequence of 64 booleans. Each bool represents a move down the tree
*/
def moves(index: Long): Seq[Boolean] = for (i <- 63 to 0 by -1) yield (index & (1L << i)) != 0
def moves(index: Long): Vector[Boolean] = (for (i <- 63 to 0 by -1) yield (index & (1L << i)) != 0).toVector
/**
*
@ -39,7 +41,7 @@ object ShaChain {
def shaChainFromSeed(hash: BinaryData, index: Long) = derive(Node(hash, 0, None), index).value
type Index = Seq[Boolean]
type Index = Vector[Boolean]
val empty = ShaChain(Map.empty[Index, BinaryData])
@ -93,6 +95,26 @@ object ShaChain {
}
}
}
val shaChainCodec: Codec[ShaChain] = {
import scodec.Codec
import scodec.bits.BitVector
import scodec.codecs._
// codec for a single map entry (i.e. Vector[Boolean] -> BinaryData
val entryCodec = vectorOfN(uint16, bool) ~ Codecs.varsizebinarydata
// codec for a Map[Vector[Boolean], BinaryData]: write all k ->v pairs using the codec defined above
val mapCodec: Codec[Map[Vector[Boolean], BinaryData]] = Codec[Map[Vector[Boolean], BinaryData]] (
(m: Map[Vector[Boolean], BinaryData]) => vectorOfN(uint16, entryCodec).encode(m.toVector),
(b: BitVector) => vectorOfN(uint16, entryCodec).decode(b).map(_.map(_.toMap))
)
// our shachain codec
(("knownHashes" | mapCodec) :: ("lastIndex" | optional(bool, int64))).as[ShaChain]
}
}
/**
@ -102,7 +124,7 @@ object ShaChain {
* @param lastIndex index of the last known hash. Hashes are supposed to be added in reverse order i.e.
* from 0xFFFFFFFFFFFFFFFF down to 0
*/
case class ShaChain(knownHashes: Map[Seq[Boolean], BinaryData], lastIndex: Option[Long] = None) {
case class ShaChain(knownHashes: Map[Vector[Boolean], BinaryData], lastIndex: Option[Long] = None) {
def addHash(hash: BinaryData, index: Long): ShaChain = ShaChain.addHash(this, hash, index)
def getHash(index: Long) = ShaChain.getHash(this, index)

View file

@ -0,0 +1,22 @@
package fr.acinq.eclair.db
/**
* Created by fabrice on 25/02/17.
*/
import fr.acinq.bitcoin.BinaryData
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.eclair.channel.{Data, State}
import fr.acinq.eclair.crypto.TransportHandler
case class ChannelState(remotePubKey: PublicKey, state: State, data: Data) {
def serialize = ChannelState.serializer.serialize(this)
}
object ChannelState {
val serializer = new TransportHandler.Serializer[ChannelState] {
def serialize(cs: ChannelState): BinaryData = JavaSerializer.serialize(cs)
def deserialize(input: BinaryData): ChannelState = JavaSerializer.deserialize[ChannelState](input)
}
}

View file

@ -0,0 +1,24 @@
package fr.acinq.eclair.db
import java.io.{ByteArrayInputStream, ByteArrayOutputStream, ObjectInputStream, ObjectOutputStream}
import fr.acinq.bitcoin.BinaryData
/**
* Created by fabrice on 17/02/17.
*/
object JavaSerializer {
def serialize[T](cs: T): BinaryData = {
val bos = new ByteArrayOutputStream()
val oos = new ObjectOutputStream(bos)
oos.writeObject(cs)
bos.toByteArray
}
def deserialize[T](input: BinaryData): T = {
val bis = new ByteArrayInputStream(input)
val osi = new ObjectInputStream(bis)
osi.readObject().asInstanceOf[T]
}
}

View file

@ -0,0 +1,29 @@
package fr.acinq.eclair.db
import fr.acinq.bitcoin.BinaryData
import fr.acinq.eclair.crypto.TransportHandler
/**
* Created by fabrice on 25/02/17.
*/
trait SimpleDb {
// @formatter:off
def put(k: String, v: BinaryData) : Unit
def get(k: String) : Option[BinaryData]
def delete(k: String) : Boolean
def keys: Seq[String]
def values: Seq[BinaryData]
// @formatter:on
}
class SimpleTypedDb[K, V](id2string: K => String, string2id: String => Option[K], serializer: TransportHandler.Serializer[V], db: SimpleDb) {
// @formatter:off
def put(k: K, v: V) = db.put(id2string(k), serializer.serialize(v))
def get(k: K): Option[V] = db.get(id2string(k)).map(serializer.deserialize)
def delete(k: K) : Boolean = db.delete(id2string(k))
def keys: Seq[K] = db.keys.map(string2id).flatten
def values: Seq[V] = keys.map(get).flatten
// @formatter:on
}

View file

@ -0,0 +1,43 @@
package fr.acinq.eclair.db
import java.io.File
import java.nio.file.{Files, Paths}
import fr.acinq.bitcoin.BinaryData
import grizzled.slf4j.Logging
import scala.util.Try
/**
* Created by fabrice on 25/02/17.
*/
object SimpleFileDb {
def expandUserHomeDirectory(fileName: String): String = {
if (fileName == "~") System.getProperty("user.home")
else if (fileName.startsWith("~/")) System.getProperty("user.home") + fileName.drop(1)
else fileName
}
}
case class SimpleFileDb(directory: String) extends SimpleDb with Logging {
import SimpleFileDb._
val root = expandUserHomeDirectory(directory)
new File(root).mkdirs()
override def put(key: String, value: BinaryData): Unit = {
logger.debug(s"put $key -> $value")
Files.write(Paths.get(root, key), value)
}
override def get(key: String): Option[BinaryData] = Try(Files.readAllBytes(Paths.get(root, key))).toOption.map(a => BinaryData(a))
override def delete(key: String): Boolean = Paths.get(root, key).toFile.delete()
override def keys: Seq[String] = new File(root).list()
override def values: Seq[BinaryData] = keys.map(get).flatten
}

View file

@ -42,6 +42,7 @@ class FxApp extends Application with Logging {
val guiUpdater = setup.system.actorOf(Props(classOf[GUIUpdater], primaryStage, controller, setup), "gui-updater")
setup.system.eventStream.subscribe(guiUpdater, classOf[ChannelEvent])
setup.system.eventStream.subscribe(guiUpdater, classOf[NetworkEvent])
setup.boostrap
import scala.concurrent.ExecutionContext.Implicits.global
setup.fatalEventFuture onSuccess {

View file

@ -8,11 +8,13 @@ import javafx.scene.layout.VBox
import javafx.stage.Stage
import akka.actor.{Actor, ActorLogging, ActorRef}
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin._
import fr.acinq.eclair.channel._
import fr.acinq.eclair.gui.controllers.{ChannelPaneController, MainController, PeerChannel, PeerNode}
import fr.acinq.eclair.io.Reconnect
import fr.acinq.eclair.router.{ChannelDiscovered, ChannelLost, NodeDiscovered, NodeLost}
import fr.acinq.eclair.transactions.CommitmentSpec
import fr.acinq.eclair.{Globals, Setup}
import org.jgrapht.graph.{DefaultEdge, SimpleGraph}
@ -31,64 +33,72 @@ class GUIUpdater(primaryStage: Stage, mainController: MainController, setup: Set
def receive: Receive = main(Map())
def createChannelPanel(channel: ActorRef, peer: ActorRef, remoteNodeId: PublicKey, isFunder: Boolean, temporaryChannelId: Long): (ChannelPaneController, VBox) = {
log.info(s"new channel: $channel")
val loader = new FXMLLoader(getClass.getResource("/gui/main/channelPane.fxml"))
val channelPaneController = new ChannelPaneController(s"$remoteNodeId")
loader.setController(channelPaneController)
val root = loader.load[VBox]
channelPaneController.nodeId.setText(s"$remoteNodeId")
channelPaneController.channelId.setText(java.lang.Long.toHexString(temporaryChannelId))
channelPaneController.funder.setText(if (isFunder) "Yes" else "No")
channelPaneController.reconnect.setDisable(!isFunder)
channelPaneController.close.setOnAction(new EventHandler[ActionEvent] {
override def handle(event: ActionEvent) = channel ! CMD_CLOSE(None)
})
channelPaneController.reconnect.setOnAction(new EventHandler[ActionEvent] {
override def handle(event: ActionEvent) = peer ! Reconnect
})
(channelPaneController, root)
}
def updateBalance(channelPaneController: ChannelPaneController, commitments: Commitments) = {
val spec = commitments.localCommit.spec
channelPaneController.capacity.setText(s"${millisatoshi2millibtc(MilliSatoshi(spec.totalFunds)).amount}")
channelPaneController.amountUs.setText(s"${millisatoshi2millibtc(MilliSatoshi(spec.toLocalMsat)).amount}")
channelPaneController.balanceBar.setProgress(spec.toLocalMsat.toDouble / spec.totalFunds)
}
def main(m: Map[ActorRef, ChannelPaneController]): Receive = {
case ChannelCreated(_, peer, channel, params, theirNodeId) =>
log.info(s"new channel: $channel")
val loader = new FXMLLoader(getClass.getResource("/gui/main/channelPane.fxml"))
val channelPaneController = new ChannelPaneController(theirNodeId.toBin.toString, params)
loader.setController(channelPaneController)
val root = loader.load[VBox]
channelPaneController.nodeId.setText(s"$theirNodeId")
channelPaneController.funder.setText(if (params.isFunder) "Yes" else "No")
channelPaneController.close.setOnAction(new EventHandler[ActionEvent] {
override def handle(event: ActionEvent) = channel ! CMD_CLOSE(None)
})
channelPaneController.reconnect.setOnAction(new EventHandler[ActionEvent] {
override def handle(event: ActionEvent) = peer ! Reconnect
})
case ChannelCreated(channel, peer, remoteNodeId, isFunder, temporaryChannelId) =>
val (channelPaneController, root) = createChannelPanel(channel, peer, remoteNodeId, isFunder, temporaryChannelId)
Platform.runLater(new Runnable() {
override def run = mainController.channelBox.getChildren.addAll(root)
})
context.become(main(m + (channel -> channelPaneController)))
case ChannelIdAssigned(channel, channelId, capacity) =>
val channelPane = m(channel)
case ChannelRestored(channel, peer, remoteNodeId, isFunder, channelId, currentData) =>
val (channelPaneController, root) = createChannelPanel(channel, peer, remoteNodeId, isFunder, channelId)
currentData match {
case d: HasCommitments => updateBalance(channelPaneController, d.commitments)
case _ => {}
}
Platform.runLater(new Runnable() {
override def run = mainController.channelBox.getChildren.addAll(root)
})
context.become(main(m + (channel -> channelPaneController)))
case ChannelIdAssigned(channel, channelId) if m.contains(channel) =>
val channelPaneController = m(channel)
Platform.runLater(new Runnable() {
override def run = {
channelPane.channelId.setText(s"$channelId")
channelPane.capacity.setText(s"${satoshi2millibtc(capacity).amount}")
channelPane.funder.getText match {
case "Yes" => channelPane.amountUs.setText(s"${satoshi2millibtc(capacity).amount}")
case "No" => channelPane.amountUs.setText("0")
}
channelPaneController.channelId.setText(java.lang.Long.toHexString(channelId))
}
})
case ChannelChangedState(channel, _, _, previousState, currentState, currentData) if m.contains(channel) =>
val channelPane = m(channel)
case ChannelStateChanged(channel, _, _, _, currentState, _) if m.contains(channel) =>
val channelPaneController = m(channel)
Platform.runLater(new Runnable() {
override def run = {
if (channelPane.channelParams.isFunder && OFFLINE.equals(currentState)) {
channelPane.reconnect.setDisable(false)
} else {
channelPane.reconnect.setDisable(true)
}
channelPane.state.setText(currentState.toString)
channelPaneController.state.setText(currentState.toString)
}
})
case ChannelSignatureReceived(channel, commitments) =>
val channelPane = m(channel)
val bal = commitments.localCommit.spec.toLocalMsat.toDouble / (commitments.localCommit.spec.toLocalMsat.toDouble + commitments.localCommit.spec.toRemoteMsat.toDouble)
case ChannelSignatureReceived(channel, commitments) if m.contains(channel) =>
val channelPaneController = m(channel)
Platform.runLater(new Runnable() {
override def run = {
channelPane.amountUs.setText(s"${satoshi2millibtc(Satoshi(commitments.localCommit.spec.toLocalMsat / 1000L)).amount}")
channelPane.balanceBar.setProgress(bal)
}
override def run = updateBalance(channelPaneController, commitments)
})
case NodeDiscovered(nodeAnnouncement) =>

View file

@ -13,7 +13,7 @@ import grizzled.slf4j.Logging
/**
* Created by DPA on 23/09/2016.
*/
class ChannelPaneController(theirNodeIdValue: String, val channelParams: LocalParams) extends Logging {
class ChannelPaneController(theirNodeIdValue: String) extends Logging {
@FXML var channelId: TextField = _
@FXML var balanceBar: ProgressBar = _

View file

@ -6,7 +6,8 @@ import akka.actor.{ActorRef, LoggingFSM, PoisonPill, Props, Terminated}
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.{BinaryData, Crypto, DeterministicWallet}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.TransportHandler.{HandshakeCompleted, Listener}
import fr.acinq.eclair.crypto.TransportHandler.{HandshakeCompleted, Listener, Serializer}
import fr.acinq.eclair.db.{JavaSerializer, SimpleDb, SimpleTypedDb}
import fr.acinq.eclair.io.Switchboard.{NewChannel, NewConnection}
import fr.acinq.eclair.router.SendRoutingState
import fr.acinq.eclair.wire._
@ -34,6 +35,8 @@ case object DISCONNECTED extends State
case object INITIALIZING extends State
case object CONNECTED extends State
case class PeerRecord(id: PublicKey, address: Option[InetSocketAddress])
// @formatter:on
/**
@ -43,9 +46,21 @@ class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, address_opt: Option[
import Peer._
val db = nodeParams.db
val peerDb = makePeerDb(db)
startWith(DISCONNECTED, DisconnectedData(Nil))
when(DISCONNECTED) {
case Event(c: ChannelRecord, d@DisconnectedData(offlineChannels)) if c.state.remotePubKey != remoteNodeId =>
log.warning(s"received channel data for the wrong peer ${c.state.remotePubKey}")
stay
case Event(c: ChannelRecord, d@DisconnectedData(offlineChannels)) =>
val (channel, _) = createChannel(nodeParams, null, c.id, false, 0) // TODO: fixme using a dedicated restore message
channel ! INPUT_RESTORED(c.id, c.state)
stay using d.copy(offlineChannels = offlineChannels :+ HotChannel(c.id, channel))
case Event(c: NewChannel, d@DisconnectedData(offlineChannels)) =>
stay using d.copy(offlineChannels = offlineChannels :+ BrandNewChannel(c))
@ -184,4 +199,20 @@ object Peer {
localFeatures = nodeParams.localFeatures
)
def makePeerDb(db: SimpleDb): SimpleTypedDb[PublicKey, PeerRecord] = {
def peerid2String(id: PublicKey) = s"peer-$id"
def string2peerid(s: String) = if (s.startsWith("peer-")) Some(PublicKey(BinaryData(s.stripPrefix("peer-")))) else None
new SimpleTypedDb[PublicKey, PeerRecord](
peerid2String,
string2peerid,
new Serializer[PeerRecord] {
override def serialize(t: PeerRecord): BinaryData = JavaSerializer.serialize(t)
override def deserialize(bin: BinaryData): PeerRecord = JavaSerializer.deserialize[PeerRecord](bin)
},
db
)
}
}

View file

@ -5,8 +5,10 @@ import java.net.InetSocketAddress
import akka.actor.{Actor, ActorLogging, ActorRef, Props, Status, Terminated}
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin.{BinaryData, MilliSatoshi, Satoshi, ScriptElt}
import fr.acinq.eclair.channel.ChannelRecord
import fr.acinq.eclair.{Globals, NodeParams}
import fr.acinq.eclair.crypto.TransportHandler.HandshakeCompleted
import fr.acinq.eclair.db.{ChannelState, SimpleDb}
/**
* Ties network connections to peers.
@ -16,12 +18,27 @@ class Switchboard(nodeParams: NodeParams, watcher: ActorRef, router: ActorRef, r
import Switchboard._
def db = nodeParams.db
val peerDb = Peer.makePeerDb(db)
def receive: Receive = main(Map(), Map())
def main(peers: Map[PublicKey, ActorRef], connections: Map[PublicKey, ActorRef]): Receive = {
case PeerRecord(publicKey, address) if peers.contains(publicKey) => ()
case PeerRecord(publicKey, address) =>
val peer = createPeer(publicKey, address)
context become main(peers + (publicKey -> peer), connections)
case ChannelRecord(id, ChannelState(remotePubKey, _, _)) if !peers.contains(remotePubKey) =>
log.warning(s"received channel data for unknown peer $remotePubKey")
case channelRecord: ChannelRecord => peers(channelRecord.state.remotePubKey) forward channelRecord
case NewConnection(publicKey, _, _) if publicKey == nodeParams.privateKey.publicKey =>
sender ! Status.Failure(new RuntimeException("cannot open connection with oneself"))
case NewConnection(remoteNodeId, address, newChannel_opt) =>
val connection = connections.get(remoteNodeId) match {
@ -32,7 +49,7 @@ class Switchboard(nodeParams: NodeParams, watcher: ActorRef, router: ActorRef, r
case None =>
log.info(s"connecting to $remoteNodeId @ $address")
val connection = context.actorOf(Client.props(nodeParams, self, address, remoteNodeId, sender))
context watch(connection)
context watch (connection)
connection
}
val peer = peers.get(remoteNodeId) match {
@ -57,7 +74,10 @@ class Switchboard(nodeParams: NodeParams, watcher: ActorRef, router: ActorRef, r
}
def createPeer(remoteNodeId: PublicKey, address_opt: Option[InetSocketAddress]) = context.actorOf(Peer.props(nodeParams, remoteNodeId, address_opt, watcher, router, relayer, defaultFinalScriptPubKey), name = s"peer-$remoteNodeId")
def createPeer(remoteNodeId: PublicKey, address_opt: Option[InetSocketAddress]) = {
peerDb.put(remoteNodeId, PeerRecord(remoteNodeId, address_opt))
context.actorOf(Peer.props(nodeParams, remoteNodeId, address_opt, watcher, router, relayer, defaultFinalScriptPubKey), name = s"peer-$remoteNodeId")
}
}
object Switchboard {

View file

@ -33,7 +33,7 @@ case class ForwardFail(fail: UpdateFailHtlc)
*/
class Relayer(nodeSecret: PrivateKey, paymentHandler: ActorRef) extends Actor with ActorLogging {
context.system.eventStream.subscribe(self, classOf[ChannelChangedState])
context.system.eventStream.subscribe(self, classOf[ChannelStateChanged])
override def receive: Receive = main(Set(), Map())
@ -41,18 +41,18 @@ class Relayer(nodeSecret: PrivateKey, paymentHandler: ActorRef) extends Actor wi
def main(channels: Set[OutgoingChannel], bindings: Map[DownstreamHtlcId, Origin]): Receive = {
case ChannelChangedState(channel, _, remoteNodeId, _, NORMAL, d: DATA_NORMAL) =>
case ChannelStateChanged(channel, _, remoteNodeId, _, NORMAL, d: DATA_NORMAL) =>
import d.commitments.channelId
log.info(s"adding channel $channelId to available channels")
context become main(channels + OutgoingChannel(channelId, channel, remoteNodeId.hash160), bindings)
case ChannelChangedState(channel, _, remoteNodeId, _, NEGOTIATING, d: DATA_NEGOTIATING) =>
case ChannelStateChanged(channel, _, remoteNodeId, _, NEGOTIATING, d: DATA_NEGOTIATING) =>
import d.commitments.channelId
log.info(s"removing channel $channelId from available channels")
// TODO: cleanup bindings
context become main(channels - OutgoingChannel(channelId, channel, remoteNodeId.hash160), bindings)
case ChannelChangedState(channel, _, remoteNodeId, _, CLOSING, d: DATA_CLOSING) =>
case ChannelStateChanged(channel, _, remoteNodeId, _, CLOSING, d: DATA_CLOSING) =>
import d.commitments.channelId
log.info(s"removing channel $channelId from available channels")
// TODO: cleanup bindings

View file

@ -10,6 +10,8 @@ import fr.acinq.bitcoin.Script.{pay2wsh, write}
import fr.acinq.eclair._
import fr.acinq.eclair.blockchain.{GetTx, GetTxResponse, WatchEventSpent, WatchSpent}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.TransportHandler.Serializer
import fr.acinq.eclair.db.{JavaSerializer, SimpleDb, SimpleTypedDb}
import fr.acinq.eclair.transactions.Scripts
import fr.acinq.eclair.wire._
import org.jgrapht.alg.shortestpath.DijkstraShortestPath
@ -34,14 +36,17 @@ case class SendRoutingState(to: ActorRef)
* Created by PM on 24/05/2016.
*/
class Router(watcher: ActorRef) extends Actor with ActorLogging {
class Router(watcher: ActorRef, db: SimpleDb) extends Actor with ActorLogging {
import Router._
val routerDb = makeRouterDb(db)
import ExecutionContext.Implicits.global
context.system.scheduler.schedule(10 seconds, 60 seconds, self, 'tick_broadcast)
def receive: Receive = main(nodes = Map(), channels = Map(), updates = Map(), rebroadcast = Nil, awaiting = Set(), stash = Nil)
def receive: Receive = main(State.empty)
def mainWithLog(nodes: Map[BinaryData, NodeAnnouncement],
channels: Map[Long, ChannelAnnouncement],
@ -50,18 +55,19 @@ class Router(watcher: ActorRef) extends Actor with ActorLogging {
awaiting: Set[ChannelAnnouncement],
stash: Seq[RoutingMessage]) = {
log.info(s"current status channels=${channels.size} nodes=${nodes.size} updates=${updates.size}")
main(nodes, channels, updates, rebroadcast, awaiting, stash)
val state = State(nodes, channels, updates, rebroadcast, awaiting, stash)
routerDb.put("router.state", state.fixme)
main(state)
}
def main(
nodes: Map[BinaryData, NodeAnnouncement],
channels: Map[Long, ChannelAnnouncement],
updates: Map[ChannelDesc, ChannelUpdate],
rebroadcast: Seq[RoutingMessage],
awaiting: Set[ChannelAnnouncement],
stash: Seq[RoutingMessage]): Receive = {
def main(state: State): Receive = {
case newState: State =>
newState.nodes.values.map(n => self ! n)
newState.channels.values.map(c => self ! c)
context become main(newState)
case SendRoutingState(remote) =>
import state._
log.info(s"info sending all announcements to $remote: channels=${channels.size} nodes=${nodes.size} updates=${updates.size}")
channels.values.foreach(remote ! _)
updates.values.foreach(remote ! _)
@ -72,19 +78,24 @@ class Router(watcher: ActorRef) extends Actor with ActorLogging {
log.error(s"bad signature for announcement $c")
sender ! Error(0, "bad announcement sig!!!".getBytes())
case c: ChannelAnnouncement if channels.containsKey(c.channelId) =>
case c: ChannelAnnouncement if state.channels.containsKey(c.channelId) =>
log.debug(s"ignoring $c (duplicate)")
case c: ChannelAnnouncement =>
import state._
val (blockHeight, txIndex, outputIndex) = fromShortId(c.channelId)
log.info(s"retrieving raw tx with blockHeight=$blockHeight and txIndex=$txIndex")
watcher ! GetTx(blockHeight, txIndex, outputIndex, c)
context become main(nodes, channels, updates, rebroadcast, awaiting + c, stash)
val state1 = state.copy(awaiting = awaiting + c)
routerDb.put("router.state", state1.fixme)
context become main(state1)
case GetTxResponse(tx, isSpendable, c: ChannelAnnouncement) if !isSpendable =>
log.debug(s"ignoring $c (funding tx spent)")
case GetTxResponse(tx, _, c: ChannelAnnouncement) =>
import state._
// TODO: check sigs
// TODO: blacklist if already received same channel id and different node ids
val (_, _, outputIndex) = fromShortId(c.channelId)
@ -108,7 +119,8 @@ class Router(watcher: ActorRef) extends Actor with ActorLogging {
log.error(s"bad signature for announcement $n")
sender ! Error(0, "bad announcement sig!!!".getBytes())
case WatchEventSpent(BITCOIN_FUNDING_OTHER_CHANNEL_SPENT(channelId), tx) if channels.containsKey(channelId) =>
case WatchEventSpent(BITCOIN_FUNDING_OTHER_CHANNEL_SPENT(channelId), tx) if state.channels.containsKey(channelId) =>
import state._
val lostChannel = channels(channelId)
log.info(s"funding tx of channelId=$channelId has been spent by txid=${tx.txid}")
log.info(s"removed channel channelId=$channelId")
@ -126,37 +138,42 @@ class Router(watcher: ActorRef) extends Actor with ActorLogging {
val lostNodes = isNodeLost(lostChannel.nodeId1).toSeq ++ isNodeLost(lostChannel.nodeId2).toSeq
context become mainWithLog(nodes -- lostNodes, channels - channelId, updates.filterKeys(_.id != channelId), rebroadcast, awaiting, stash)
case n: NodeAnnouncement if awaiting.size > 0 =>
context become main(nodes, channels, updates, rebroadcast, awaiting, stash :+ n)
case n: NodeAnnouncement if state.awaiting.size > 0 =>
val state1 = state.copy(stash = state.stash :+ n)
routerDb.put("router.state", state1.fixme)
context become main(state1)
case n: NodeAnnouncement if !channels.values.exists(c => c.nodeId1 == n.nodeId || c.nodeId2 == n.nodeId) =>
case n: NodeAnnouncement if !state.channels.values.exists(c => c.nodeId1 == n.nodeId || c.nodeId2 == n.nodeId) =>
log.debug(s"ignoring $n (no related channel found)")
case n: NodeAnnouncement if nodes.containsKey(n.nodeId) && nodes(n.nodeId).timestamp >= n.timestamp =>
case n: NodeAnnouncement if state.nodes.containsKey(n.nodeId) && state.nodes(n.nodeId).timestamp >= n.timestamp =>
log.debug(s"ignoring announcement $n (old timestamp or duplicate)")
case n: NodeAnnouncement if nodes.containsKey(n.nodeId) =>
case n: NodeAnnouncement if state.nodes.containsKey(n.nodeId) =>
import state._
log.info(s"updated node nodeId=${n.nodeId}")
context.system.eventStream.publish(NodeUpdated(n))
context become mainWithLog(nodes + (n.nodeId -> n), channels, updates, rebroadcast :+ n, awaiting, stash)
case n: NodeAnnouncement =>
import state._
log.info(s"added node nodeId=${n.nodeId}")
context.system.eventStream.publish(NodeDiscovered(n))
context become mainWithLog(nodes + (n.nodeId -> n), channels, updates, rebroadcast :+ n, awaiting, stash)
case u: ChannelUpdate if awaiting.size > 0 =>
context become main(nodes, channels, updates, rebroadcast, awaiting, stash :+ u)
case u: ChannelUpdate if state.awaiting.size > 0 =>
context become main(state.copy(stash = state.stash :+ u))
case u: ChannelUpdate if !channels.contains(u.channelId) =>
case u: ChannelUpdate if !state.channels.contains(u.channelId) =>
log.debug(s"ignoring $u (no related channel found)")
case u: ChannelUpdate if !Announcements.checkSig(u, getDesc(u, channels(u.channelId)).a) =>
case u: ChannelUpdate if !Announcements.checkSig(u, getDesc(u, state.channels(u.channelId)).a) =>
// TODO: (dirty) this will make the origin channel close the connection
log.error(s"bad signature for announcement $u")
sender ! Error(0, "bad announcement sig!!!".getBytes())
case u: ChannelUpdate =>
import state._
val channel = channels(u.channelId)
val desc = getDesc(u, channel)
if (updates.contains(desc) && updates(desc).timestamp >= u.timestamp) {
@ -165,23 +182,26 @@ class Router(watcher: ActorRef) extends Actor with ActorLogging {
context become mainWithLog(nodes, channels, updates + (desc -> u), rebroadcast :+ u, awaiting, stash)
}
case 'tick_broadcast if rebroadcast.size == 0 =>
case 'tick_broadcast if state.rebroadcast.size == 0 =>
// no-op
case 'tick_broadcast =>
import state._
log.info(s"broadcasting ${rebroadcast.size} routing messages")
rebroadcast.foreach(context.actorSelection(Register.actorPathToPeers) ! _)
context become main(nodes, channels, updates, Nil, awaiting, stash)
val state1 = State(nodes, channels, updates, Nil, awaiting, stash)
routerDb.put("router.state", state1.fixme)
context become main(state1)
case 'nodes => sender ! nodes.values
case 'nodes => sender ! state.nodes.values
case 'channels => sender ! channels.values
case 'channels => sender ! state.channels.values
case 'updates => sender ! updates.values
case 'updates => sender ! state.updates.values
case 'dot => graph2dot(nodes, channels) pipeTo sender
case 'dot => graph2dot(state.nodes, state.channels) pipeTo sender
case RouteRequest(start, end) => findRoute(start, end, updates).map(RouteResponse(_)) pipeTo sender
case RouteRequest(start, end) => findRoute(start, end, state.updates).map(RouteResponse(_)) pipeTo sender
case other => log.warning(s"unhandled message $other")
}
@ -190,7 +210,22 @@ class Router(watcher: ActorRef) extends Actor with ActorLogging {
object Router {
def props(watcher: ActorRef) = Props(classOf[Router], watcher)
def props(watcher: ActorRef, db: SimpleDb) = Props(classOf[Router], watcher, db)
def makeRouterDb(db: SimpleDb) = {
// we use a single key: router.state
new SimpleTypedDb[String, State](
_ => "router.state",
s => if (s == "router.state") Some("router.state") else None,
new Serializer[State] {
override def serialize(t: State): BinaryData = JavaSerializer.serialize(t.fixme)
override def deserialize(bin: BinaryData): State = JavaSerializer.deserialize[State](bin)
},
db
)
}
def getDesc(u: ChannelUpdate, channel: ChannelAnnouncement): ChannelDesc = {
require(u.flags.data.size == 2, s"invalid flags length ${u.flags.data.size} != 2")
@ -251,4 +286,20 @@ object Router {
}
}
case class State(nodes: Map[BinaryData, NodeAnnouncement],
channels: Map[Long, ChannelAnnouncement],
updates: Map[ChannelDesc, ChannelUpdate],
rebroadcast: Seq[RoutingMessage],
awaiting: Set[ChannelAnnouncement],
stash: Seq[RoutingMessage]) {
// see http://stackoverflow.com/questions/32900862/map-can-not-be-serializable-in-scala :(
// we alse remove transient fields (awaiting and stash)
def fixme = this.copy(nodes = nodes.map(identity), channels = channels.map(identity), updates = updates.map(identity), awaiting = Set(), stash = Seq())
}
object State {
val empty = State(nodes = Map(), channels = Map(), updates = Map(), rebroadcast = Nil, awaiting = Set(), stash = Nil)
}
}

View file

@ -22,9 +22,9 @@ object Codecs {
// (for something smarter see https://github.com/yzernik/bitcoin-scodec/blob/master/src/main/scala/io/github/yzernik/bitcoinscodec/structures/UInt64.scala)
val uint64: Codec[Long] = int64.narrow(l => if (l >= 0) Attempt.Successful(l) else Attempt.failure(Err(s"overflow for value $l")), l => l)
def binarydata(size: Int): Codec[BinaryData] = bytes(size).xmap(d => BinaryData(d.toSeq), d => ByteVector(d.data))
def binarydata(size: Int): Codec[BinaryData] = bytes(size).xmap(d => BinaryData(d.toArray), d => ByteVector(d.data))
def varsizebinarydata: Codec[BinaryData] = variableSizeBytes(uint16, bytes.xmap(d => BinaryData(d.toSeq), d => ByteVector(d.data)))
def varsizebinarydata: Codec[BinaryData] = variableSizeBytes(uint16, bytes.xmap(d => BinaryData(d.toArray), d => ByteVector(d.data)))
def listofsignatures: Codec[List[BinaryData]] = listOfN(uint16, signature)

View file

@ -4,6 +4,7 @@ import java.net.InetSocketAddress
import fr.acinq.bitcoin.Crypto.PrivateKey
import fr.acinq.bitcoin.{BinaryData, DeterministicWallet, Script}
import fr.acinq.eclair.db.DummyDb
import fr.acinq.eclair.io.Peer
/**
@ -36,7 +37,8 @@ object TestConstants {
feeBaseMsat = 546000,
feeProportionalMillionth = 10,
reserveToFundingRatio = 0.01, // note: not used (overriden below)
maxReserveToFundingRatio = 0.05)
maxReserveToFundingRatio = 0.05,
db = new DummyDb())
val id = nodeParams.privateKey.publicKey
val channelParams = Peer.makeChannelParams(
nodeParams = nodeParams,
@ -71,7 +73,8 @@ object TestConstants {
feeBaseMsat = 546000,
feeProportionalMillionth = 10,
reserveToFundingRatio = 0.01, // note: not used (overriden below)
maxReserveToFundingRatio = 0.05)
maxReserveToFundingRatio = 0.05,
db = new DummyDb)
val id = nodeParams.privateKey.publicKey
val channelParams = Peer.makeChannelParams(
nodeParams = nodeParams,

View file

@ -64,7 +64,7 @@ class ThroughputSpec extends FunSuite {
val latch = new CountDownLatch(2)
val listener = system.actorOf(Props(new Actor {
override def receive: Receive = {
case ChannelChangedState(_, _, _, _, NORMAL, _) => latch.countDown()
case ChannelStateChanged(_, _, _, _, NORMAL, _) => latch.countDown()
}
}), "listener")
system.eventStream.subscribe(listener, classOf[ChannelEvent])

View file

@ -5,6 +5,7 @@ import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
import fr.acinq.eclair.channel.{WAIT_FOR_FUNDING_INTERNAL, _}
import fr.acinq.eclair.db.DummyDb
import fr.acinq.eclair.wire.{AcceptChannel, Error, Init, OpenChannel}
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
import org.junit.runner.RunWith

View file

@ -3,6 +3,7 @@ package fr.acinq.eclair.channel.states.a
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.db.DummyDb
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
import fr.acinq.eclair.wire.{Error, Init, OpenChannel}
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}

View file

@ -49,7 +49,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
commitments = initialState.commitments.copy(
localNextHtlcId = 1,
localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil),
unackedMessages = htlc :: Nil
unackedMessages = initialState.commitments.unackedMessages :+ htlc
)))
relayer.expectMsg(Bind(htlc, origin = Local(sender.ref)))
}
@ -84,7 +84,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
commitments = initialState.commitments.copy(
localNextHtlcId = 1,
localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil),
unackedMessages = htlc :: Nil)))
unackedMessages = initialState.commitments.unackedMessages :+ htlc)))
relayer.expectMsg(Bind(htlc, origin = Relayed(originHtlc)))
}
}

View file

@ -79,7 +79,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
awaitCond(bob.stateData == initialState.copy(
commitments = initialState.commitments.copy(
localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fulfill),
unackedMessages = fulfill :: Nil)))
unackedMessages = initialState.commitments.unackedMessages :+ fulfill)))
}
}
@ -148,7 +148,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
awaitCond(bob.stateData == initialState.copy(
commitments = initialState.commitments.copy(
localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail),
unackedMessages = fail :: Nil)))
unackedMessages = initialState.commitments.unackedMessages :+ fail)))
}
}

View file

@ -205,4 +205,22 @@ class ShaChainSpec extends FunSuite {
val chain8 = chain7.addHash(BinaryData("a7efbc61aac46d34f77778bac22c8a20c6a46ca460addc49009bda875ec88fa4"), 281474976710648L)
}
}
test("serialize/deserialize with scodec") {
val chain = ShaChain.init
val chain1 = chain.addHash(BinaryData("7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc"), 281474976710655L)
val chain2 = chain1.addHash(BinaryData("c7518c8ae4660ed02894df8976fa1a3659c1a8b4b5bec0c4b872abeba4cb8964"), 281474976710654L)
val chain3 = chain2.addHash(BinaryData("2273e227a5b7449b6e70f1fb4652864038b1cbf9cd7c043a7d6456b7fc275ad8"), 281474976710653L)
val chain4 = chain3.addHash(BinaryData("27cddaa5624534cb6cb9d7da077cf2b22ab21e9b506fd4998a51d54502e99116"), 281474976710652L)
val chain5 = chain4.addHash(BinaryData("c65716add7aa98ba7acb236352d665cab17345fe45b55fb879ff80e6bd0c41dd"), 281474976710651L)
val chain6 = chain5.addHash(BinaryData("969660042a28f32d9be17344e09374b379962d03db1574df5a8a5a47e19ce3f2"), 281474976710650L)
val chain7 = chain6.addHash(BinaryData("a5a64476122ca0925fb344bdc1854c1c0a59fc614298e50a33e331980a220f32"), 281474976710649L)
val chain8 = chain7.addHash(BinaryData("05cde6323d949933f7f7b78776bcc1ea6d9b31447732e3802e1f7ac44b650e17"), 281474976710648L)
Seq(chain, chain1, chain2, chain3, chain4, chain5, chain6, chain7, chain8).map(chain => {
val encoded = ShaChain.shaChainCodec.encode(chain)
val decoded = ShaChain.shaChainCodec.decode(encoded.toOption.get).toOption.get.value
assert(decoded == chain)
})
}
}

View file

@ -0,0 +1,126 @@
package fr.acinq.eclair.db
import java.net.InetSocketAddress
import fr.acinq.bitcoin.Crypto.{PrivateKey, Scalar}
import fr.acinq.bitcoin.{BinaryData, Crypto, MilliSatoshi, Satoshi, Transaction}
import fr.acinq.eclair.channel.Helpers.Funding
import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.{Generators, ShaChain}
import fr.acinq.eclair.transactions.Transactions.CommitTx
import fr.acinq.eclair.transactions._
import fr.acinq.eclair.wire.{CommitSig, Init, OpenChannel, UpdateAddHtlc}
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
/**
* Created by fabrice on 07/02/17.
*/
@RunWith(classOf[JUnitRunner])
class ChannelStateSpec extends FunSuite {
import ChannelStateSpec._
test("basic serialization test") {
val data = openChannel
val bin = ChannelState.serializer.serialize(data)
val check = ChannelState.serializer.deserialize(bin)
assert(data == check)
}
test("basic serialization test (NORMAL)") {
val data = normal
val bin = ChannelState.serializer.serialize(data)
val check = ChannelState.serializer.deserialize(bin)
assert(data == check)
}
}
object ChannelStateSpec {
val localParams = LocalParams(
dustLimitSatoshis = Satoshi(546).toLong,
maxHtlcValueInFlightMsat = 50,
channelReserveSatoshis = 10000,
htlcMinimumMsat = 50000,
feeratePerKw = 15000,
toSelfDelay = 144,
maxAcceptedHtlcs = 50,
fundingPrivKey = PrivateKey(BinaryData("01" * 32) :+ 1.toByte),
revocationSecret = Scalar(BinaryData("02" * 32)),
paymentKey = PrivateKey(BinaryData("03" * 32) :+ 1.toByte),
delayedPaymentKey = Scalar(BinaryData("04" * 32)),
defaultFinalScriptPubKey = Nil,
shaSeed = BinaryData("05" * 32),
isFunder = true,
globalFeatures = "foo".getBytes(),
localFeatures = "bar".getBytes())
val remoteParams = RemoteParams(
dustLimitSatoshis = Satoshi(546).toLong,
maxHtlcValueInFlightMsat = 50,
channelReserveSatoshis = 10000,
htlcMinimumMsat = 50000,
feeratePerKw = 15000,
toSelfDelay = 144,
maxAcceptedHtlcs = 50,
fundingPubKey = PrivateKey(BinaryData("01" * 32) :+ 1.toByte).publicKey,
revocationBasepoint = Scalar(BinaryData("02" * 32)).toPoint,
paymentBasepoint = Scalar(BinaryData("03" * 32)).toPoint,
delayedPaymentBasepoint = Scalar(BinaryData("04" * 32)).toPoint,
globalFeatures = "foo".getBytes(),
localFeatures = "bar".getBytes())
val openChannel = {
val open = OpenChannel(temporaryChannelId = 1000,
fundingSatoshis = 42000,
pushMsat = 0,
dustLimitSatoshis = localParams.dustLimitSatoshis,
maxHtlcValueInFlightMsat = localParams.maxHtlcValueInFlightMsat,
channelReserveSatoshis = localParams.channelReserveSatoshis,
htlcMinimumMsat = localParams.htlcMinimumMsat,
feeratePerKw = localParams.feeratePerKw,
toSelfDelay = localParams.toSelfDelay,
maxAcceptedHtlcs = localParams.maxAcceptedHtlcs,
fundingPubkey = localParams.fundingPrivKey.publicKey,
revocationBasepoint = localParams.revocationSecret.toPoint,
paymentBasepoint = localParams.paymentKey.toPoint,
delayedPaymentBasepoint = localParams.delayedPaymentKey.toPoint,
firstPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, 0))
ChannelState(
PrivateKey(BinaryData("01" * 32) :+ 1.toByte).publicKey,
WAIT_FOR_ACCEPT_CHANNEL,
DATA_WAIT_FOR_ACCEPT_CHANNEL(INPUT_INIT_FUNDER(PrivateKey(BinaryData("01" * 32) :+ 1.toByte).publicKey, 42, 10000, 1000000, localParams, Init("foo".getBytes, "bar".getBytes)), open))
}
val paymentPreimages = Seq(
BinaryData("0000000000000000000000000000000000000000000000000000000000000000"),
BinaryData("0101010101010101010101010101010101010101010101010101010101010101"),
BinaryData("0202020202020202020202020202020202020202020202020202020202020202"),
BinaryData("0303030303030303030303030303030303030303030303030303030303030303"),
BinaryData("0404040404040404040404040404040404040404040404040404040404040404")
)
val htlcs = Seq(
Htlc(IN, UpdateAddHtlc(0, 0, MilliSatoshi(1000000).amount, 500, Crypto.sha256(paymentPreimages(0)), BinaryData("")), None),
Htlc(IN, UpdateAddHtlc(0, 0, MilliSatoshi(2000000).amount, 501, Crypto.sha256(paymentPreimages(1)), BinaryData("")), None),
Htlc(OUT, UpdateAddHtlc(0, 0, MilliSatoshi(2000000).amount, 502, Crypto.sha256(paymentPreimages(2)), BinaryData("")), None),
Htlc(OUT, UpdateAddHtlc(0, 0, MilliSatoshi(3000000).amount, 503, Crypto.sha256(paymentPreimages(3)), BinaryData("")), None),
Htlc(IN, UpdateAddHtlc(0, 0, MilliSatoshi(4000000).amount, 504, Crypto.sha256(paymentPreimages(4)), BinaryData("")), None)
)
val fundingTx = Transaction.read("0200000001adbb20ea41a8423ea937e76e8151636bf6093b70eaff942930d20576600521fd000000006b48304502210090587b6201e166ad6af0227d3036a9454223d49a1f11839c1a362184340ef0240220577f7cd5cca78719405cbf1de7414ac027f0239ef6e214c90fcaab0454d84b3b012103535b32d5eb0a6ed0982a0479bbadc9868d9836f6ba94dd5a63be16d875069184ffffffff028096980000000000220020c015c4a6be010e21657068fc2e6a9d02b27ebe4d490a25846f7237f104d1a3cd20256d29010000001600143ca33c2e4446f4a305f23c80df8ad1afdcf652f900000000")
val fundingAmount = fundingTx.txOut(0).amount
val commitmentInput = Funding.makeFundingInputInfo(fundingTx.hash, 0, fundingAmount, localParams.fundingPrivKey.publicKey, remoteParams.fundingPubKey)
val localCommit = LocalCommit(0, CommitmentSpec(htlcs.toSet, 1500, 50000, 700000), PublishableTxs(CommitTx(commitmentInput, Transaction(2, Nil, Nil, 0)), Nil), CommitSig(42, BinaryData("01" * 64), Nil))
val remoteCommit = RemoteCommit(0, CommitmentSpec(htlcs.toSet, 1500, 50000, 700000), BinaryData("0303030303030303030303030303030303030303030303030303030303030303"), Scalar(BinaryData("04" * 32)).toPoint)
val commitments = Commitments(localParams, remoteParams, localCommit, remoteCommit, LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil),
localNextHtlcId = 0L,
remoteNextCommitInfo = Right(null), // TODO: we will receive their next per-commitment point in the next message, so we temporarily put an empty byte array
remoteNextHtlcId = 0L,
commitInput = commitmentInput, remotePerCommitmentSecrets = ShaChain.init, channelId = 0L, unackedMessages = Nil)
val normal = ChannelState(PrivateKey(BinaryData("01" * 32) :+ 1.toByte).publicKey, NORMAL, DATA_NORMAL(commitments, None))
}

View file

@ -0,0 +1,20 @@
package fr.acinq.eclair.db
import fr.acinq.bitcoin.BinaryData
/**
* Created by fabrice on 20/02/17.
*/
class DummyDb extends SimpleDb {
val map = collection.mutable.HashMap.empty[String, BinaryData]
override def put(key: String, value: BinaryData): Unit = map.put(key, value)
override def get(key: String): Option[BinaryData] = map.get(key)
override def delete(key: String): Boolean = map.remove(key).isDefined
override def keys: Seq[String] = map.keys.toSeq
override def values: Seq[BinaryData] = map.values.toSeq
}

View file

@ -45,7 +45,7 @@ class RelayerSpec extends TestkitBaseClass {
test("add a channel") { case (relayer, _) =>
val sender = TestProbe()
val channel_bc = TestProbe()
sender.send(relayer, ChannelChangedState(channel_bc.ref, null, nodeId_c, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(null, Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_bc), null)))
sender.send(relayer, ChannelStateChanged(channel_bc.ref, null, nodeId_c, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_bc), null)))
sender.send(relayer, 'channels)
val upstreams = sender.expectMsgType[Set[OutgoingChannel]]
assert(upstreams === Set(OutgoingChannel(channelId_bc, channel_bc.ref, nodeId_c.hash160)))
@ -55,12 +55,12 @@ class RelayerSpec extends TestkitBaseClass {
val sender = TestProbe()
val channel_bc = TestProbe()
sender.send(relayer, ChannelChangedState(channel_bc.ref, null, nodeId_c, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(null, Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_bc), null)))
sender.send(relayer, ChannelStateChanged(channel_bc.ref, null, nodeId_c, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_bc), null)))
sender.send(relayer, 'channels)
val upstreams1 = sender.expectMsgType[Set[OutgoingChannel]]
assert(upstreams1 === Set(OutgoingChannel(channelId_bc, channel_bc.ref, nodeId_c.hash160)))
sender.send(relayer, ChannelChangedState(channel_bc.ref, null, nodeId_c, SHUTDOWN, NEGOTIATING, DATA_NEGOTIATING(null, Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_bc), null, null, null)))
sender.send(relayer, ChannelStateChanged(channel_bc.ref, null, nodeId_c, SHUTDOWN, NEGOTIATING, DATA_NEGOTIATING(Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_bc), null, null, null)))
sender.send(relayer, 'channels)
val upstreams2 = sender.expectMsgType[Set[OutgoingChannel]]
assert(upstreams2 === Set.empty)
@ -70,12 +70,12 @@ class RelayerSpec extends TestkitBaseClass {
val sender = TestProbe()
val channel_bc = TestProbe()
sender.send(relayer, ChannelChangedState(channel_bc.ref, null, nodeId_c, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(null, Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_bc), null)))
sender.send(relayer, ChannelStateChanged(channel_bc.ref, null, nodeId_c, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_bc), null)))
sender.send(relayer, 'channels)
val upstreams1 = sender.expectMsgType[Set[OutgoingChannel]]
assert(upstreams1 === Set(OutgoingChannel(channelId_bc, channel_bc.ref, nodeId_c.hash160)))
sender.send(relayer, ChannelChangedState(channel_bc.ref, null, nodeId_c, NORMAL, CLOSING, DATA_CLOSING(Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_bc), None, Some(null), None, None, None, Nil)))
sender.send(relayer, ChannelStateChanged(channel_bc.ref, null, nodeId_c, NORMAL, CLOSING, DATA_CLOSING(Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_bc), None, Some(null), None, None, None, Nil)))
sender.send(relayer, 'channels)
val upstreams2 = sender.expectMsgType[Set[OutgoingChannel]]
@ -113,7 +113,7 @@ class RelayerSpec extends TestkitBaseClass {
UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.expiry, cmd.paymentHash, cmd.onion)
}
sender.send(relayer, ChannelChangedState(channel_bc.ref, null, nodeId_c, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(null, Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_bc), null)))
sender.send(relayer, ChannelStateChanged(channel_bc.ref, null, nodeId_c, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_bc), null)))
sender.send(relayer, ForwardAdd(add_ab))
sender.expectNoMsg(1 second)
@ -177,8 +177,8 @@ class RelayerSpec extends TestkitBaseClass {
UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.expiry, cmd.paymentHash, cmd.onion)
}
sender.send(relayer, ChannelChangedState(channel_ab.ref, null, nodeId_a, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(null, Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_ab), null)))
sender.send(relayer, ChannelChangedState(channel_bc.ref, null, nodeId_c, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(null, Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_bc), null)))
sender.send(relayer, ChannelStateChanged(channel_ab.ref, null, nodeId_a, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_ab), null)))
sender.send(relayer, ChannelStateChanged(channel_bc.ref, null, nodeId_c, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_bc), null)))
sender.send(relayer, ForwardAdd(add_ab))
val cmd_bc = channel_bc.expectMsgType[CMD_ADD_HTLC]
val add_bc = UpdateAddHtlc(channelId = channelId_bc, id = 987451, amountMsat = cmd_bc.amountMsat, expiry = cmd_bc.expiry, paymentHash = cmd_bc.paymentHash, onionRoutingPacket = cmd_bc.onion)
@ -204,8 +204,8 @@ class RelayerSpec extends TestkitBaseClass {
UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.expiry, cmd.paymentHash, cmd.onion)
}
sender.send(relayer, ChannelChangedState(channel_ab.ref, null, nodeId_a, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(null, Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_ab), null)))
sender.send(relayer, ChannelChangedState(channel_bc.ref, null, nodeId_c, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(null, Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_bc), null)))
sender.send(relayer, ChannelStateChanged(channel_ab.ref, null, nodeId_a, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_ab), null)))
sender.send(relayer, ChannelStateChanged(channel_bc.ref, null, nodeId_c, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_bc), null)))
sender.send(relayer, ForwardAdd(add_ab))
val cmd_bc = channel_bc.expectMsgType[CMD_ADD_HTLC]
val add_bc = UpdateAddHtlc(channelId = channelId_bc, id = 987451, amountMsat = cmd_bc.amountMsat, expiry = cmd_bc.expiry, paymentHash = cmd_bc.paymentHash, onionRoutingPacket = cmd_bc.onion)
@ -230,8 +230,8 @@ class RelayerSpec extends TestkitBaseClass {
UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.expiry, cmd.paymentHash, cmd.onion)
}
sender.send(relayer, ChannelChangedState(channel_ab.ref, null, nodeId_a, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(null, Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_ab), null)))
sender.send(relayer, ChannelChangedState(channel_bc.ref, null, nodeId_c, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(null, Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_bc), null)))
sender.send(relayer, ChannelStateChanged(channel_ab.ref, null, nodeId_a, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_ab), null)))
sender.send(relayer, ChannelStateChanged(channel_bc.ref, null, nodeId_c, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_bc), null)))
sender.send(relayer, ForwardAdd(add_ab))
val cmd_bc = channel_bc.expectMsgType[CMD_ADD_HTLC]
val add_bc = UpdateAddHtlc(channelId = channelId_bc, id = 987451, amountMsat = cmd_bc.amountMsat, expiry = cmd_bc.expiry, paymentHash = cmd_bc.paymentHash, onionRoutingPacket = cmd_bc.onion)
@ -257,8 +257,8 @@ class RelayerSpec extends TestkitBaseClass {
UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.expiry, cmd.paymentHash, cmd.onion)
}
sender.send(relayer, ChannelChangedState(channel_ab.ref, null, nodeId_a, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(null, Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_ab), null)))
sender.send(relayer, ChannelChangedState(channel_bc.ref, null, nodeId_c, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(null, Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_bc), null)))
sender.send(relayer, ChannelStateChanged(channel_ab.ref, null, nodeId_a, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_ab), null)))
sender.send(relayer, ChannelStateChanged(channel_bc.ref, null, nodeId_c, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(Commitments(null, null, null, null, null, null, 0, 0, null, null, null, null, channelId_bc), null)))
sender.send(relayer, ForwardAdd(add_ab))
val cmd_bc = channel_bc.expectMsgType[CMD_ADD_HTLC]
val add_bc = UpdateAddHtlc(channelId = channelId_bc, id = 987451, amountMsat = cmd_bc.amountMsat, expiry = cmd_bc.expiry, paymentHash = cmd_bc.paymentHash, onionRoutingPacket = cmd_bc.onion)

View file

@ -6,6 +6,7 @@ import fr.acinq.bitcoin.Crypto.PrivateKey
import fr.acinq.bitcoin.Script.{pay2wsh, write}
import fr.acinq.bitcoin.{Satoshi, Transaction, TxOut}
import fr.acinq.eclair.blockchain.{GetTx, GetTxResponse, WatchSpent}
import fr.acinq.eclair.db.{DummyDb, SimpleDb}
import fr.acinq.eclair.router.Announcements._
import fr.acinq.eclair.transactions.Scripts
import fr.acinq.eclair.wire._
@ -67,7 +68,7 @@ abstract class BaseRouterSpec extends TestkitBaseClass {
within(30 seconds) {
// first we set up the router
val watcher = TestProbe()
val router = system.actorOf(Router.props(watcher.ref))
val router = system.actorOf(Router.props(watcher.ref, new DummyDb))
// we announce channels
router ! chan_ab
router ! chan_bc

View file

@ -46,7 +46,7 @@
<scala.version.short>2.11</scala.version.short>
<akka.version>2.4.16</akka.version>
<acinqtools.version>1.2</acinqtools.version>
<bitcoinlib.version>0.9.9</bitcoinlib.version>
<bitcoinlib.version>0.9.10-SNAPSHOT</bitcoinlib.version>
</properties>
<build>