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:
commit
b4af63b728
37 changed files with 896 additions and 302 deletions
|
@ -24,6 +24,9 @@ eclair {
|
|||
b = 170
|
||||
}
|
||||
}
|
||||
db {
|
||||
root = "~/.eclair"
|
||||
}
|
||||
delay-blocks = 144
|
||||
mindepth-blocks = 3
|
||||
expiry-delta-blocks = 144
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 _ => "???"
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
}
|
||||
}
|
29
eclair-node/src/main/scala/fr/acinq/eclair/db/SimpleDb.scala
Normal file
29
eclair-node/src/main/scala/fr/acinq/eclair/db/SimpleDb.scala
Normal 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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -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 = _
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
20
eclair-node/src/test/scala/fr/acinq/eclair/db/DummyDb.scala
Normal file
20
eclair-node/src/test/scala/fr/acinq/eclair/db/DummyDb.scala
Normal 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
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue