1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-02-23 06:35:11 +01:00

announcement are now signed, better handling of relayed htlcs

This commit is contained in:
pm47 2017-02-03 11:48:15 +01:00
parent a33f2dc703
commit 92d297b3d9
18 changed files with 297 additions and 188 deletions

View file

@ -29,7 +29,7 @@ eclair {
expiry-delta-blocks = 144
htlc-minimum-msat = 1000000
fee-base-msat = 546000
fee-proportional-msat = 10
fee-proportional-millionth = 10
payment-handler = "local"
}
akka {

View file

@ -87,7 +87,7 @@ class Setup() extends Logging {
case "noop" => system.actorOf(Props[NoopPaymentHandler], name = "payment-handler")
}
val relayer = system.actorOf(Relayer.props(Globals.Node.privateKey, paymentHandler), name = "propagator")
val router = system.actorOf(Router.props(watcher, Globals.Node.announcement), name = "router")
val router = system.actorOf(Router.props(watcher), name = "router")
val paymentInitiator = system.actorOf(PaymentInitiator.props(Globals.Node.publicKey, router, blockCount), "payment-initiator")
val register = system.actorOf(Register.props(watcher, router, relayer, finalScriptPubKey), name = "register")
val server = system.actorOf(Server.props(config.getString("eclair.server.host"), config.getInt("eclair.server.port"), register), "server")

View file

@ -27,7 +27,6 @@ object Globals {
val alias = config.getString("node.alias").take(32)
val color: (Byte, Byte, Byte) = (config.getInt("node.color.r").toByte, config.getInt("node.color.g").toByte, config.getInt("node.color.b").toByte)
val address = new InetSocketAddress(config.getString("server.host"), config.getInt("server.port"))
val announcement = Router.makeNodeAnnouncement(privateKey, alias, color, address :: Nil, Platform.currentTime / 1000)
}
val expiry_delta_blocks = config.getInt("expiry-delta-blocks")
@ -36,7 +35,7 @@ object Globals {
val mindepth_blocks = config.getInt("mindepth-blocks")
val feeratePerKw = 10000
val fee_base_msat = config.getInt("fee-base-msat")
val fee_proportional_msat = config.getInt("fee-proportional-msat")
val fee_proportional_millionth = config.getInt("fee-proportional-millionth")
val default_anchor_amount = 1000000
val autosign_interval = 300 milliseconds

View file

@ -8,6 +8,7 @@ import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.channel.Helpers.{Closing, Funding}
import fr.acinq.eclair.crypto.{Generators, ShaChain}
import fr.acinq.eclair.payment.Binding
import fr.acinq.eclair.router.{Announcements, Router}
import fr.acinq.eclair.transactions._
import fr.acinq.eclair.wire._
@ -25,9 +26,9 @@ object Channel {
def props(them: ActorRef, blockchain: ActorRef, router: ActorRef, relayer: ActorRef, localParams: LocalParams, theirNodeId: PublicKey, autoSignInterval: Option[FiniteDuration] = None) = Props(new Channel(them, blockchain, router, relayer, localParams, theirNodeId, autoSignInterval))
}
class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, relayer: ActorRef, val localParams: LocalParams, theirNodeId: PublicKey, autoSignInterval: Option[FiniteDuration] = None)(implicit ec: ExecutionContext = ExecutionContext.Implicits.global) extends LoggingFSM[State, Data] {
class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, relayer: ActorRef, val localParams: LocalParams, remoteNodeId: PublicKey, autoSignInterval: Option[FiniteDuration] = None)(implicit ec: ExecutionContext = ExecutionContext.Implicits.global) extends LoggingFSM[State, Data] {
context.system.eventStream.publish(ChannelCreated(self, localParams, theirNodeId))
context.system.eventStream.publish(ChannelCreated(self, localParams, remoteNodeId))
/*
8888888 888b 888 8888888 88888888888
@ -285,7 +286,8 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
val channelId = toShortId(blockHeight, txIndex, commitments.commitInput.outPoint.index.toInt)
blockchain ! WatchLost(self, commitments.anchorId, params.minimumDepth, BITCOIN_FUNDING_LOST)
val nextPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, 1)
them ! FundingLocked(temporaryChannelId, channelId, None, None, nextPerCommitmentPoint) // TODO: routing announcements disabled
val (localNodeSig, localBitcoinSig) = Announcements.signChannelAnnouncement(d.channelId, Globals.Node.privateKey, remoteNodeId, d.params.localParams.fundingPrivKey, d.params.remoteParams.fundingPubKey)
them ! FundingLocked(temporaryChannelId, channelId, Some(localNodeSig), Some(localBitcoinSig), nextPerCommitmentPoint)
deferred.map(self ! _)
// TODO: htlcIdx should not be 0 when resuming connection
goto(WAIT_FOR_FUNDING_LOCKED) using DATA_NORMAL(params, commitments.copy(channelId = channelId), None)
@ -321,9 +323,23 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
// 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(_, remoteChannelId, _, _, nextPerCommitmentPoint), d: DATA_NORMAL) =>
case Event(FundingLocked(_, _, remoteNodeSig_opt, remoteBitcoinSig_opt, nextPerCommitmentPoint), d: DATA_NORMAL) =>
log.info(s"channel ready with channelId=${java.lang.Long.toUnsignedString(d.channelId)}")
Register.createAlias(theirNodeId.hash160, d.channelId)
Register.createAlias(remoteNodeId.hash160, d.channelId)
(remoteNodeSig_opt, remoteBitcoinSig_opt) match {
case (Some(remoteNodeSig), Some(remoteBitcoinSig)) =>
log.info(s"announcing channel ${d.channelId} on the network")
val (localNodeSig, localBitcoinSig) = Announcements.signChannelAnnouncement(d.channelId, Globals.Node.privateKey, remoteNodeId, d.params.localParams.fundingPrivKey, d.params.remoteParams.fundingPubKey)
val channelAnn = Announcements.makeChannelAnnouncement(d.channelId, Globals.Node.publicKey, remoteNodeId, d.params.localParams.fundingPrivKey.publicKey, d.params.remoteParams.fundingPubKey, localNodeSig, remoteNodeSig, localBitcoinSig, remoteBitcoinSig)
val nodeAnn = Announcements.makeNodeAnnouncement(Globals.Node.privateKey, Globals.Node.alias, Globals.Node.color, Globals.Node.address :: Nil, Platform.currentTime / 1000)
val channelUpdate = Announcements.makeChannelUpdate(Globals.Node.privateKey, remoteNodeId, d.commitments.channelId, Globals.expiry_delta_blocks, Globals.htlc_minimum_msat, Globals.fee_base_msat, Globals.fee_proportional_millionth, Platform.currentTime / 1000)
router ! channelAnn
router ! nodeAnn
router ! channelUpdate
// let's trigger the broadcast immediately so that we don't wait for 60 seconds to announce our newly created channel
router ! 'tick_broadcast
case _ => log.info(s"channel ${d.channelId} won't be announced")
}
goto(NORMAL) using d.copy(commitments = d.commitments.copy(remoteNextCommitInfo = Right(nextPerCommitmentPoint)))
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_NORMAL) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
@ -353,15 +369,15 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
888 888 d88P 888 8888888 888 Y888 88888888 "Y88888P" "Y88888P" 888
*/
when(NORMAL) (handleExceptions {
when(NORMAL)(handleExceptions {
case Event(c: CMD_ADD_HTLC, d: DATA_NORMAL) if d.localShutdown.isDefined =>
handleCommandError(sender, new RuntimeException("cannot send new htlcs, closing in progress"))
case Event(c@CMD_ADD_HTLC(amountMsat, rHash, expiry, route, origin_opt, id_opt, do_commit), d@DATA_NORMAL(params, commitments, _)) =>
case Event(c@CMD_ADD_HTLC(amountMsat, rHash, expiry, route, origin, id_opt, do_commit), d@DATA_NORMAL(params, commitments, _)) =>
Try(Commitments.sendAdd(commitments, c)) match {
case Success((commitments1, add)) =>
origin_opt.map(origin => relayer ! Binding(origin, add))
relayer ! Binding(add, origin)
if (do_commit) self ! CMD_SIGN
handleCommandSuccess(sender, add, d.copy(commitments = commitments1))
case Failure(cause) => handleCommandError(sender, cause)
@ -512,7 +528,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
"Y8888P" 88888888 "Y88888P" "Y8888P" 8888888 888 Y888 "Y8888P88
*/
when(SHUTDOWN) (handleExceptions {
when(SHUTDOWN)(handleExceptions {
case Event(c@CMD_FULFILL_HTLC(id, r, do_commit), d: DATA_SHUTDOWN) =>
Try(Commitments.sendFulfill(d.commitments, c)) match {
@ -594,7 +610,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
})
when(NEGOTIATING) (handleExceptions {
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 {
@ -693,7 +709,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
stay
case Event(CMD_GETINFO, _) =>
sender ! RES_GETINFO(theirNodeId, stateData match {
sender ! RES_GETINFO(remoteNodeId, stateData match {
// TODO
case c: DATA_WAIT_FOR_OPEN_CHANNEL => 0L
case c: DATA_WAIT_FOR_ACCEPT_CHANNEL => c.temporaryChannelId
@ -712,7 +728,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
}
onTransition {
case previousState -> currentState => context.system.eventStream.publish(ChannelChangedState(self, context.parent, theirNodeId, previousState, currentState, stateData))
case previousState -> currentState => context.system.eventStream.publish(ChannelChangedState(self, context.parent, remoteNodeId, previousState, currentState, stateData))
}
/*

View file

@ -2,6 +2,7 @@ package fr.acinq.eclair.channel
import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, PublicKey, Scalar}
import fr.acinq.bitcoin.{BinaryData, ScriptElt, Transaction}
import fr.acinq.eclair.payment.{Local, Origin}
import fr.acinq.eclair.transactions.CommitmentSpec
import fr.acinq.eclair.transactions.Transactions.CommitTx
import fr.acinq.eclair.wire.{ClosingSigned, FundingLocked, Shutdown, UpdateAddHtlc}
@ -87,7 +88,7 @@ sealed trait Command
/**
* @param id should only be provided in tests otherwise it will be assigned automatically
*/
final case class CMD_ADD_HTLC(amountMsat: Long, paymentHash: BinaryData, expiry: Long, onion: BinaryData = BinaryData("00" * 1254), origin: Option[UpdateAddHtlc] = None, id: Option[Long] = None, commit: Boolean = false) extends Command
final case class CMD_ADD_HTLC(amountMsat: Long, paymentHash: BinaryData, expiry: Long, onion: BinaryData = BinaryData("00" * 1254), origin: Origin = Local, id: Option[Long] = None, commit: Boolean = false) extends Command
final case class CMD_FULFILL_HTLC(id: Long, r: BinaryData, commit: Boolean = false) extends Command
final case class CMD_FAIL_HTLC(id: Long, reason: String, commit: Boolean = false) extends Command
case object CMD_SIGN extends Command

View file

@ -1,7 +1,9 @@
package fr.acinq
import fr.acinq.bitcoin.Crypto.PrivateKey
import fr.acinq.bitcoin._
import fr.acinq.bitcoin.{BinaryData, _}
import scodec.Attempt
import scodec.bits.BitVector
import scala.util.Random
@ -20,4 +22,9 @@ package object eclair {
bin
}, compressed = true)
def serializationResult(attempt: Attempt[BitVector]): BinaryData = attempt match {
case Attempt.Successful(bin) => BinaryData(bin.toByteArray)
case Attempt.Failure(cause) => throw new RuntimeException(s"serialization error: $cause")
}
}

View file

@ -15,7 +15,12 @@ import scala.util.{Failure, Success, Try}
// @formatter:off
case class OutgoingChannel(channelId: Long, channel: ActorRef, nodeAddress: BinaryData)
case class Binding(downstream: UpdateAddHtlc, upstream: UpdateAddHtlc)
sealed trait Origin
case object Local extends Origin
case class Relayed(downstream: UpdateAddHtlc) extends Origin
case class Binding(add: UpdateAddHtlc, origin: Origin)
// @formatter:on
@ -29,7 +34,7 @@ class Relayer(nodeSecret: PrivateKey, paymentHandler: ActorRef) extends Actor wi
override def receive: Receive = main(Set(), Map())
def main(upstreams: Set[OutgoingChannel], bindings: Map[UpdateAddHtlc, UpdateAddHtlc]): Receive = {
def main(upstreams: Set[OutgoingChannel], bindings: Map[UpdateAddHtlc, Origin]): Receive = {
case ChannelChangedState(channel, _, remoteNodeId, _, NORMAL, d: DATA_NORMAL) =>
import d.commitments.channelId
@ -60,7 +65,7 @@ class Relayer(nodeSecret: PrivateKey, paymentHandler: ActorRef) extends Actor wi
case Success((Attempt.Successful(DecodeResult(payload, _)), nextNodeAddress, nextPacket)) if upstreams.exists(_.nodeAddress == nextNodeAddress) =>
val upstream = upstreams.find(_.nodeAddress == nextNodeAddress).get.channel
log.info(s"forwarding htlc #${add.id} to upstream=$upstream")
upstream ! CMD_ADD_HTLC(payload.amt_to_forward, add.paymentHash, payload.outgoing_cltv_value, nextPacket, origin = Some(add), commit = true)
upstream ! CMD_ADD_HTLC(payload.amt_to_forward, add.paymentHash, payload.outgoing_cltv_value, nextPacket, origin = Relayed(add), commit = true)
context become main(upstreams, bindings)
case Success((Attempt.Successful(DecodeResult(_, _)), nextNodeAddress, _)) =>
log.warning(s"couldn't resolve upstream node address $nextNodeAddress, failing htlc #${add.id}")
@ -73,38 +78,45 @@ class Relayer(nodeSecret: PrivateKey, paymentHandler: ActorRef) extends Actor wi
sender ! CMD_FAIL_HTLC(add.id, "onion parsing error", commit = true)
}
case Binding(downstream: UpdateAddHtlc, upstream: UpdateAddHtlc) =>
log.info(s"relayed htlc ${downstream.channelId}/${downstream.id} to ${upstream.channelId}/${upstream.id}")
context become main(upstreams, bindings + (upstream -> downstream))
case Binding(upstream, origin) =>
origin match {
case Local => log.info(s"we are the origin of htlc ${upstream.channelId}/${upstream.id}")
case Relayed(downstream) => log.info(s"relayed htlc ${downstream.channelId}/${downstream.id} to ${upstream.channelId}/${upstream.id}")
}
context become main(upstreams, bindings + (upstream -> origin))
case (add: UpdateAddHtlc, fulfill: UpdateFulfillHtlc) =>
bindings.get(add) match {
case Some(origin) if upstreams.exists(_.channelId == origin.channelId) =>
case Some(Relayed(origin)) if upstreams.exists(_.channelId == origin.channelId) =>
val downstream = upstreams.find(_.channelId == origin.channelId).get.channel
downstream ! CMD_SIGN
downstream ! CMD_FULFILL_HTLC(origin.id, fulfill.paymentPreimage)
downstream ! CMD_SIGN
case Some(origin) =>
case Some(Relayed(origin)) =>
log.warning(s"origin channel ${origin.channelId} has disappeared in the meantime")
case None =>
case Some(Local) =>
log.info(s"we were the origin payer for htlc #${fulfill.id}")
context.system.eventStream.publish(PaymentSent(self, add.paymentHash))
case None =>
log.warning(s"no origin found for htlc $add")
}
case (add: UpdateAddHtlc, fail: UpdateFailHtlc) =>
bindings.get(add) match {
case Some(origin) if upstreams.exists(_.channelId == origin.channelId) =>
case Some(Relayed(origin)) if upstreams.exists(_.channelId == origin.channelId) =>
val downstream = upstreams.find(_.channelId == origin.channelId).get.channel
downstream ! CMD_SIGN
// TODO: fix new String(fail.reason)
downstream ! CMD_FAIL_HTLC(origin.id, new String(fail.reason))
downstream ! CMD_SIGN
case Some(origin) =>
case Some(Relayed(origin)) =>
log.warning(s"origin channel ${origin.channelId} has disappeared in the meantime")
case None =>
case Some(Local) =>
log.info(s"we were the origin payer for htlc #${fail.id}")
// TODO: fix new String(fail.reason)
context.system.eventStream.publish(PaymentFailed(self, add.paymentHash, new String(fail.reason)))
case None =>
log.warning(s"no origin found for htlc $add")
}
case 'upstreams => sender ! upstreams

View file

@ -0,0 +1,106 @@
package fr.acinq.eclair.router
import java.net.InetSocketAddress
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey, sha256, verifySignature}
import fr.acinq.bitcoin.{BinaryData, Crypto, LexicographicalOrdering}
import fr.acinq.eclair.serializationResult
import fr.acinq.eclair.wire.{ChannelAnnouncement, ChannelUpdate, Codecs, NodeAnnouncement}
import shapeless.HNil
/**
* Created by PM on 03/02/2017.
*/
object Announcements {
def channelAnnouncementWitnessEncode(channelId: Long, nodeId1: BinaryData, nodeId2: BinaryData, bitcoinKey1: BinaryData, bitcoinKey2: BinaryData): BinaryData =
sha256(sha256(serializationResult(Codecs.channelAnnouncementWitnessCodec.encode(channelId :: nodeId1 :: nodeId2 :: bitcoinKey1 :: bitcoinKey2 :: HNil))))
def nodeAnnouncementWitnessEncode(timestamp: Long, nodeId: BinaryData, rgbColor: (Byte, Byte, Byte), alias: String, features: BinaryData, addresses: List[InetSocketAddress]): BinaryData =
sha256(sha256(serializationResult(Codecs.nodeAnnouncementWitnessCodec.encode(timestamp :: nodeId :: rgbColor :: alias :: features :: addresses :: HNil))))
def channelUpdateWitnessEncode(channelId: Long, timestamp: Long, flags: BinaryData, cltvExpiryDelta: Int, htlcMinimumMsat: Long, feeBaseMsat: Long, feeProportionalMillionths: Long): BinaryData =
sha256(sha256(serializationResult(Codecs.channelUpdateWitnessCodec.encode(channelId :: timestamp :: flags :: cltvExpiryDelta :: htlcMinimumMsat :: feeBaseMsat :: feeProportionalMillionths :: HNil))))
def signChannelAnnouncement(channelId: Long, localNodeSecret: PrivateKey, remoteNodeId: PublicKey, localFundingPrivKey: PrivateKey, remoteFundingKey: PublicKey): (BinaryData, BinaryData) = {
val witness = if (LexicographicalOrdering.isLessThan(localNodeSecret.publicKey.toBin, remoteNodeId.toBin)) {
channelAnnouncementWitnessEncode(channelId, localNodeSecret.publicKey, remoteNodeId, localFundingPrivKey.publicKey, remoteFundingKey)
} else {
channelAnnouncementWitnessEncode(channelId, remoteNodeId, localNodeSecret.publicKey, remoteFundingKey, localFundingPrivKey.publicKey)
}
val nodeSig = Crypto.encodeSignature(Crypto.sign(witness, localNodeSecret)) :+ 1.toByte
val bitcoinSig = Crypto.encodeSignature(Crypto.sign(witness, localFundingPrivKey)) :+ 1.toByte
(nodeSig, bitcoinSig)
}
def makeChannelAnnouncement(channelId: Long, localNodeId: PublicKey, remoteNodeId: PublicKey, localFundingKey: PublicKey, remoteFundingKey: PublicKey, localNodeSignature: BinaryData, remoteNodeSignature: BinaryData, localBitcoinSignature: BinaryData, remoteBitcoinSignature: BinaryData): ChannelAnnouncement = {
val (nodeId1, nodeId2, bitcoinKey1, bitcoinKey2, nodeSignature1, nodeSignature2, bitcoinSignature1, bitcoinSignature2) =
if (LexicographicalOrdering.isLessThan(localNodeId.toBin, remoteNodeId.toBin)) {
(localNodeId, remoteNodeId, localFundingKey, remoteFundingKey, localNodeSignature, remoteNodeSignature, localBitcoinSignature, remoteBitcoinSignature)
} else {
(remoteNodeId, localNodeId, remoteFundingKey, localFundingKey, remoteNodeSignature, localNodeSignature, remoteBitcoinSignature, localBitcoinSignature)
}
ChannelAnnouncement(
nodeSignature1 = nodeSignature1,
nodeSignature2 = nodeSignature2,
bitcoinSignature1 = bitcoinSignature1,
bitcoinSignature2 = bitcoinSignature2,
channelId = channelId,
nodeId1 = nodeId1,
nodeId2 = nodeId2,
bitcoinKey1 = bitcoinKey1,
bitcoinKey2 = bitcoinKey2
)
}
def makeNodeAnnouncement(nodeSecret: PrivateKey, alias: String, color: (Byte, Byte, Byte), addresses: List[InetSocketAddress], timestamp: Long): NodeAnnouncement = {
require(alias.size <= 32)
val witness = nodeAnnouncementWitnessEncode(timestamp, nodeSecret.publicKey, color, alias, "", addresses)
val sig = Crypto.encodeSignature(Crypto.sign(witness, nodeSecret)) :+ 1.toByte
NodeAnnouncement(
signature = sig,
timestamp = timestamp,
nodeId = nodeSecret.publicKey,
rgbColor = color,
alias = alias,
features = "",
addresses = addresses
)
}
def makeChannelUpdate(nodeSecret: PrivateKey, remoteNodeId: PublicKey, channelId: Long, cltvExpiryDelta: Int, htlcMinimumMsat: Long, feeBaseMsat: Long, feeProportionalMillionths: Long, timestamp: Long): ChannelUpdate = {
val flags = if (LexicographicalOrdering.isLessThan(nodeSecret.publicKey.toBin, remoteNodeId.toBin)) "0000" else "0001"
val witness = channelUpdateWitnessEncode(channelId, timestamp, flags, cltvExpiryDelta, htlcMinimumMsat, feeBaseMsat, feeProportionalMillionths)
val sig = Crypto.encodeSignature(Crypto.sign(witness, nodeSecret)) :+ 1.toByte
ChannelUpdate(
signature = sig,
channelId = channelId,
timestamp = timestamp,
flags = flags,
cltvExpiryDelta = cltvExpiryDelta,
htlcMinimumMsat = htlcMinimumMsat,
feeBaseMsat = feeBaseMsat,
feeProportionalMillionths = feeProportionalMillionths
)
}
def checkSigs(ann: ChannelAnnouncement): Boolean = {
val witness = channelAnnouncementWitnessEncode(ann.channelId, ann.nodeId1, ann.nodeId2, ann.bitcoinKey1, ann.bitcoinKey2)
verifySignature(witness, ann.nodeSignature1, PublicKey(ann.nodeId1)) &&
verifySignature(witness, ann.nodeSignature2, PublicKey(ann.nodeId2)) &&
verifySignature(witness, ann.bitcoinSignature1, PublicKey(ann.bitcoinKey1)) &&
verifySignature(witness, ann.bitcoinSignature2, PublicKey(ann.bitcoinKey2))
}
def checkSig(ann: NodeAnnouncement): Boolean = {
val witness = nodeAnnouncementWitnessEncode(ann.timestamp, ann.nodeId, ann.rgbColor, ann.alias, ann.features, ann.addresses)
verifySignature(witness, ann.signature, PublicKey(ann.nodeId))
}
def checkSig(ann: ChannelUpdate, nodeId: BinaryData): Boolean = {
val witness = channelUpdateWitnessEncode(ann.channelId, ann.timestamp, ann.flags, ann.cltvExpiryDelta, ann.htlcMinimumMsat, ann.feeBaseMsat, ann.feeProportionalMillionths)
verifySignature(witness, ann.signature, PublicKey(nodeId))
}
}

View file

@ -1,19 +1,14 @@
package fr.acinq.eclair.router
import java.net.InetSocketAddress
import akka.actor.{Actor, ActorLogging, ActorRef, Props}
import akka.pattern.pipe
import fr.acinq.bitcoin.Crypto.PrivateKey
import fr.acinq.bitcoin.{BinaryData, LexicographicalOrdering}
import fr.acinq.eclair.Globals
import fr.acinq.bitcoin.BinaryData
import fr.acinq.eclair.channel._
import fr.acinq.eclair.wire._
import org.jgrapht.alg.shortestpath.DijkstraShortestPath
import org.jgrapht.graph.{DefaultDirectedGraph, DefaultEdge}
import scala.collection.JavaConversions._
import scala.compat.Platform
import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future}
@ -30,7 +25,7 @@ case class RouteResponse(hops: Seq[Hop]) { require(hops.size > 0, "route cannot
* Created by PM on 24/05/2016.
*/
class Router(watcher: ActorRef, announcement: NodeAnnouncement) extends Actor with ActorLogging {
class Router(watcher: ActorRef) extends Actor with ActorLogging {
import Router._
@ -39,12 +34,9 @@ class Router(watcher: ActorRef, announcement: NodeAnnouncement) extends Actor wi
context.system.eventStream.subscribe(self, classOf[ChannelChangedState])
context.system.scheduler.schedule(10 seconds, 60 seconds, self, 'tick_broadcast)
context.system.eventStream.publish(NodeDiscovered(announcement))
def receive: Receive = main(local = announcement, nodes = Map(announcement.nodeId -> announcement), channels = Map(), updates = Map(), rebroadcast = Nil)
def receive: Receive = main(nodes = Map(), channels = Map(), updates = Map(), rebroadcast = Nil)
def main(
local: NodeAnnouncement,
nodes: Map[BinaryData, NodeAnnouncement],
channels: Map[Long, ChannelAnnouncement],
updates: Map[ChannelDesc, ChannelUpdate],
@ -56,24 +48,6 @@ class Router(watcher: ActorRef, announcement: NodeAnnouncement) extends Actor wi
nodes.values.foreach(transport ! _)
updates.values.foreach(transport ! _)
case ChannelChangedState(_, _, remoteNodeId, _, NORMAL, d: DATA_NORMAL) =>
val (c, u) = if (LexicographicalOrdering.isLessThan(local.nodeId, remoteNodeId.toBin)) {
(
makeChannelAnnouncement(d.commitments.channelId, local.nodeId, remoteNodeId, d.params.localParams.fundingPrivKey.publicKey.toBin, d.params.remoteParams.fundingPubKey.toBin),
makeChannelUpdate(Globals.Node.privateKey, d.commitments.channelId, true, Platform.currentTime / 1000)
)
} else {
(
makeChannelAnnouncement(d.commitments.channelId, remoteNodeId, local.nodeId, d.params.remoteParams.fundingPubKey.toBin, d.params.localParams.fundingPrivKey.publicKey.toBin),
makeChannelUpdate(Globals.Node.privateKey, d.commitments.channelId, false, Platform.currentTime / 1000)
)
}
log.info(s"added channel channelId=${c.channelId} (nodes=${nodes.size} channels=${channels.size + 1})")
// let's trigger the broadcast immediately so that we don't wait for 60 seconds to announce our newly created channel
self ! 'tick_broadcast
context.system.eventStream.publish(ChannelDiscovered(c))
context become main(local, nodes, channels + (c.channelId -> c), updates, rebroadcast :+ c :+ local :+ u)
case s: ChannelChangedState =>
// other channel changed state messages are ignored
@ -89,9 +63,9 @@ class Router(watcher: ActorRef, announcement: NodeAnnouncement) extends Actor wi
//watcher ! WatchSpent(self, txId: BinaryData, outputIndex: Int, minDepth: Int, event: BitcoinEvent)
log.info(s"added channel channelId=${c.channelId} (nodes=${nodes.size} channels=${channels.size + 1})")
context.system.eventStream.publish(ChannelDiscovered(c))
context become main(local, nodes, channels + (c.channelId -> c), updates, rebroadcast :+ c)
context become main(nodes, channels + (c.channelId -> c), updates, rebroadcast :+ c)
case n: NodeAnnouncement if !checkSig(n) =>
//case n: NodeAnnouncement if !checkSig(n) =>
// TODO: fail connection (should probably be done in the auth handler or channel)
case n: NodeAnnouncement if !channels.values.exists(c => c.nodeId1 == n.nodeId || c.nodeId2 == n.nodeId) =>
@ -103,12 +77,12 @@ class Router(watcher: ActorRef, announcement: NodeAnnouncement) extends Actor wi
case n: NodeAnnouncement =>
log.info(s"added/replaced node nodeId=${n.nodeId} (nodes=${nodes.size + 1} channels=${channels.size})")
context.system.eventStream.publish(NodeDiscovered(n))
context become main(local, nodes + (n.nodeId -> n), channels, updates, rebroadcast :+ n)
context become main(nodes + (n.nodeId -> n), channels, updates, rebroadcast :+ n)
case u: ChannelUpdate if !channels.contains(u.channelId) =>
log.debug(s"ignoring $u (no related channel found)")
case u: ChannelUpdate if !checkSig(u, getDesc(u, channels(u.channelId)).a) =>
//case u: ChannelUpdate if !checkSig(u, getDesc(u, channels(u.channelId)).a) =>
// TODO: fail connection (should probably be done in the auth handler or channel)
case u: ChannelUpdate =>
@ -117,7 +91,7 @@ class Router(watcher: ActorRef, announcement: NodeAnnouncement) extends Actor wi
if (updates.contains(desc) && updates(desc).timestamp >= u.timestamp) {
log.debug(s"ignoring $u (old timestamp or duplicate)")
} else {
context become main(local, nodes, channels, updates + (desc -> u), rebroadcast :+ u)
context become main(nodes, channels, updates + (desc -> u), rebroadcast :+ u)
}
case 'tick_broadcast if rebroadcast.size == 0 =>
@ -126,7 +100,7 @@ class Router(watcher: ActorRef, announcement: NodeAnnouncement) extends Actor wi
case 'tick_broadcast =>
log.info(s"broadcasting ${rebroadcast.size} routing messages")
rebroadcast.foreach(context.actorSelection(Register.actorPathToTransportHandlers) ! _)
context become main(local, nodes, channels, updates, Nil)
context become main(nodes, channels, updates, Nil)
case 'nodes => sender ! nodes.values
@ -142,77 +116,7 @@ class Router(watcher: ActorRef, announcement: NodeAnnouncement) extends Actor wi
object Router {
def props(watcher: ActorRef, announcement: NodeAnnouncement) = Props(classOf[Router], watcher, announcement)
// TODO: placeholder for signatures, we don't actually sign for now
val DUMMY_SIG = BinaryData("3045022100e0a180fdd0fe38037cc878c03832861b40a29d32bd7b40b10c9e1efc8c1468a002205ae06d1624896d0d29f4b31e32772ea3cb1b4d7ed4e077e5da28dcc33c0e781201")
def makeChannelAnnouncement(channelId: Long, nodeId1: BinaryData, nodeId2: BinaryData, fundingKey1: BinaryData, fundingKey2: BinaryData): ChannelAnnouncement = {
val unsigned = ChannelAnnouncement(
nodeSignature1 = DUMMY_SIG,
nodeSignature2 = DUMMY_SIG,
channelId = channelId,
bitcoinSignature1 = DUMMY_SIG,
bitcoinSignature2 = DUMMY_SIG,
nodeId1 = nodeId1,
nodeId2 = nodeId2,
bitcoinKey1 = fundingKey1,
bitcoinKey2 = fundingKey2
)
unsigned
}
def makeNodeAnnouncement(secret: PrivateKey, alias: String, color: (Byte, Byte, Byte), addresses: List[InetSocketAddress], timestamp: Long): NodeAnnouncement = {
require(alias.size <= 32)
val unsigned = NodeAnnouncement(
signature = DUMMY_SIG,
timestamp = timestamp,
nodeId = secret.publicKey.toBin,
rgbColor = color,
alias = alias,
features = "",
addresses = addresses
)
unsigned
/*val bin = Codecs.nodeAnnouncementCodec.encode(unsigned).toOption.map(_.toByteArray).getOrElse(throw new RuntimeException(s"cannot encode $unsigned"))
val hash = sha256(sha256(bin.drop(64)))
val sig = encodeSignature(sign(hash, secret))
unsigned.copy(signature = sig)*/
}
def makeChannelUpdate(secret: PrivateKey, channelId: Long, isNodeId1: Boolean, timestamp: Long): ChannelUpdate = {
val unsigned = ChannelUpdate(
signature = DUMMY_SIG,
channelId = channelId,
timestamp = timestamp,
flags = if (isNodeId1) "0000" else "0001",
cltvExpiryDelta = Globals.expiry_delta_blocks,
htlcMinimumMsat = Globals.htlc_minimum_msat,
feeBaseMsat = Globals.fee_base_msat,
feeProportionalMillionths = Globals.fee_proportional_msat
)
unsigned
/*val bin = Codecs.channelUpdateCodec.encode(unsigned).toOption.map(_.toByteArray).getOrElse(throw new RuntimeException(s"cannot encode $unsigned"))
val hash = sha256(sha256(bin.drop(64)))
val sig = encodeSignature(sign(hash, secret))
unsigned.copy(signature = sig)*/
}
def checkSig(ann: NodeAnnouncement): Boolean = true
/*{
val bin = Codecs.nodeAnnouncementCodec.encode(ann).toOption.map(_.toByteArray).getOrElse(throw new RuntimeException(s"cannot encode $ann"))
val hash = sha256(sha256(bin.drop(64)))
verifySignature(hash, ann.signature, PublicKey(ann.nodeId))
}*/
def checkSig(ann: ChannelUpdate, nodeId: BinaryData): Boolean = true
/*{
val bin = Codecs.channelUpdateCodec.encode(ann).toOption.map(_.toByteArray).getOrElse(throw new RuntimeException(s"cannot encode $ann"))
val hash = sha256(sha256(bin.drop(64)))
verifySignature(hash, ann.signature, PublicKey(nodeId))
}*/
def props(watcher: ActorRef) = Props(classOf[Router], watcher)
def getDesc(u: ChannelUpdate, channel: ChannelAnnouncement): ChannelDesc = {
require(u.flags.data.size == 2, s"invalid flags length ${u.flags.data.size} != 2")

View file

@ -10,6 +10,7 @@ import fr.acinq.eclair.wire
import scodec.bits.{BitVector, ByteVector}
import scodec.codecs._
import scodec.{Attempt, Codec, Err}
import shapeless._, ops.hlist._
/**
@ -188,35 +189,44 @@ object Codecs {
("channelId" | int64) ::
("feeratePerKw" | uint32)).as[UpdateFee]
val channelAnnouncementCodec: Codec[ChannelAnnouncement] = (
("nodeSignature1" | signature) ::
("nodeSignature2" | signature) ::
val channelAnnouncementWitnessCodec = (
("channelId" | int64) ::
("bitcoinSignature1" | signature) ::
("bitcoinSignature2" | signature) ::
("nodeId1" | binarydata(33)) ::
("nodeId2" | binarydata(33)) ::
("bitcoinKey1" | binarydata(33)) ::
("bitcoinKey2" | binarydata(33))).as[ChannelAnnouncement]
("bitcoinKey2" | binarydata(33)))
val nodeAnnouncementCodec: Codec[NodeAnnouncement] = (
("signature" | signature) ::
val channelAnnouncementCodec: Codec[ChannelAnnouncement] = (
("nodeSignature1" | signature) ::
("nodeSignature2" | signature) ::
("bitcoinSignature1" | signature) ::
("bitcoinSignature2" | signature) ::
channelAnnouncementWitnessCodec).as[ChannelAnnouncement]
val nodeAnnouncementWitnessCodec = (
("timestamp" | uint32) ::
("nodeId" | binarydata(33)) ::
("rgbColor" | rgb) ::
("alias" | zeropaddedstring(32)) ::
("features" | varsizebinarydata) ::
("addresses" | listofsocketaddresses)).as[NodeAnnouncement]
("addresses" | listofsocketaddresses))
val channelUpdateCodec: Codec[ChannelUpdate] = (
val nodeAnnouncementCodec: Codec[NodeAnnouncement] = (
("signature" | signature) ::
nodeAnnouncementWitnessCodec).as[NodeAnnouncement]
val channelUpdateWitnessCodec = (
("channelId" | int64) ::
("timestamp" | uint32) ::
("flags" | binarydata(2)) ::
("cltvExpiryDelta" | uint16) ::
("htlcMinimumMsat" | uint32) ::
("feeBaseMsat" | uint32) ::
("feeProportionalMillionths" | uint32)).as[ChannelUpdate]
("feeProportionalMillionths" | uint32))
val channelUpdateCodec: Codec[ChannelUpdate] = (
("signature" | signature) ::
channelUpdateWitnessCodec).as[ChannelUpdate]
val lightningMessageCodec = discriminated[LightningMessage].by(uint16)
.typecase(16, initCodec)

View file

@ -104,9 +104,9 @@ case class UpdateFee(channelId: Long,
case class ChannelAnnouncement(nodeSignature1: BinaryData,
nodeSignature2: BinaryData,
channelId: Long,
bitcoinSignature1: BinaryData,
bitcoinSignature2: BinaryData,
channelId: Long,
nodeId1: BinaryData,
nodeId2: BinaryData,
bitcoinKey1: BinaryData,

View file

@ -2,11 +2,13 @@ package fr.acinq.eclair.channel.states.c
import akka.actor.{ActorRef, Props}
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.bitcoin.Crypto
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.channel._
import fr.acinq.eclair.router.Router
import fr.acinq.eclair.wire._
import fr.acinq.eclair.{TestkitBaseClass, TestBitcoinClient, TestConstants}
import fr.acinq.eclair.{TestBitcoinClient, TestConstants, TestkitBaseClass}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
@ -18,7 +20,7 @@ import scala.concurrent.duration._
@RunWith(classOf[JUnitRunner])
class WaitForFundingLockedStateSpec extends TestkitBaseClass {
type FixtureParam = Tuple6[TestFSMRef[State, Data, Channel], TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe, ActorRef]
type FixtureParam = Tuple7[TestFSMRef[State, Data, Channel], TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe, ActorRef, TestProbe]
override def withFixture(test: OneArgTest) = {
val alice2bob = TestProbe()
@ -57,18 +59,25 @@ class WaitForFundingLockedStateSpec extends TestkitBaseClass {
awaitCond(alice.stateName == WAIT_FOR_FUNDING_LOCKED)
awaitCond(bob.stateName == WAIT_FOR_FUNDING_LOCKED)
}
test((alice, bob, alice2bob, bob2alice, alice2blockchain, blockchainA))
test((alice, bob, alice2bob, bob2alice, alice2blockchain, blockchainA, router))
}
test("recv FundingLocked") { case (alice, _, alice2bob, bob2alice, alice2blockchain, _) =>
test("recv FundingLocked") { case (alice, _, alice2bob, bob2alice, alice2blockchain, _, router) =>
within(30 seconds) {
bob2alice.expectMsgType[FundingLocked]
bob2alice.forward(alice)
awaitCond(alice.stateName == NORMAL)
val channelAnnouncement = router.expectMsgType[ChannelAnnouncement]
val nodeAnnouncement = router.expectMsgType[NodeAnnouncement]
val channelUpdate = router.expectMsgType[ChannelUpdate]
//assert(Router.checkSigs(channelAnnouncement))
//assert(Router.checkSig(nodeAnnouncement))
// TODO: test should not use global key
//assert(Router.checkSig(channelUpdate, TestConstants.Alice.id))
}
}
test("recv FundingLocked (channel id mismatch") { case (alice, _, alice2bob, bob2alice, alice2blockchain, _) =>
test("recv FundingLocked (channel id mismatch") { case (alice, _, alice2bob, bob2alice, alice2blockchain, _, router) =>
within(30 seconds) {
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
val fundingLocked = bob2alice.expectMsgType[FundingLocked]
@ -80,7 +89,7 @@ class WaitForFundingLockedStateSpec extends TestkitBaseClass {
}
}
test("recv BITCOIN_FUNDING_SPENT (remote commit)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
test("recv BITCOIN_FUNDING_SPENT (remote commit)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, router) =>
within(30 seconds) {
// bob publishes his commitment tx
val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
@ -90,7 +99,7 @@ class WaitForFundingLockedStateSpec extends TestkitBaseClass {
}
}
test("recv BITCOIN_FUNDING_SPENT (other commit)") { case (alice, _, alice2bob, bob2alice, alice2blockchain, _) =>
test("recv BITCOIN_FUNDING_SPENT (other commit)") { case (alice, _, alice2bob, bob2alice, alice2blockchain, _, router) =>
within(30 seconds) {
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, null)
@ -100,7 +109,7 @@ class WaitForFundingLockedStateSpec extends TestkitBaseClass {
}
}
test("recv Error") { case (alice, _, alice2bob, bob2alice, alice2blockchain, _) =>
test("recv Error") { case (alice, _, alice2bob, bob2alice, alice2blockchain, _, router) =>
within(30 seconds) {
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
alice ! Error(0, "oops".getBytes)
@ -110,7 +119,7 @@ class WaitForFundingLockedStateSpec extends TestkitBaseClass {
}
}
test("recv CMD_CLOSE") { case (alice, _, alice2bob, bob2alice, alice2blockchain, _) =>
test("recv CMD_CLOSE") { case (alice, _, alice2bob, bob2alice, alice2blockchain, _, router) =>
within(30 seconds) {
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
alice ! CMD_CLOSE(None)

View file

@ -8,7 +8,7 @@ import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
import fr.acinq.eclair.channel.{Data, State, _}
import fr.acinq.eclair.payment.Binding
import fr.acinq.eclair.payment.{Binding, Local, Relayed}
import fr.acinq.eclair.transactions.{IN, OUT}
import fr.acinq.eclair.wire.{ClosingSigned, CommitSig, Error, RevokeAndAck, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFulfillHtlc}
import fr.acinq.eclair.{TestBitcoinClient, TestConstants, TestkitBaseClass}
@ -48,13 +48,13 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
val sender = TestProbe()
val h = BinaryData("00112233445566778899aabbccddeeff")
sender.send(alice, CMD_ADD_HTLC(50000000, h, 144, origin = None))
sender.send(alice, CMD_ADD_HTLC(50000000, h, 144))
sender.expectMsg("ok")
val htlc = alice2bob.expectMsgType[UpdateAddHtlc]
assert(htlc.id == 1 && htlc.paymentHash == h)
awaitCond(alice.stateData == initialState.copy(
commitments = initialState.commitments.copy(localCurrentHtlcId = 1, localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil))))
relayer.expectNoMsg()
relayer.expectMsg(Binding(htlc, origin = Local))
}
}
@ -64,15 +64,14 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
val sender = TestProbe()
val h = BinaryData("00112233445566778899aabbccddeeff")
val originHtlc = UpdateAddHtlc(channelId = 4298564, id = 5656, amountMsat = 50000000, expiry = 144, paymentHash = h, onionRoutingPacket = "00" * 1254)
val cmd = CMD_ADD_HTLC(originHtlc.amountMsat - 10000, h, originHtlc.expiry - 7, origin = Some(originHtlc))
val cmd = CMD_ADD_HTLC(originHtlc.amountMsat - 10000, h, originHtlc.expiry - 7, origin = Relayed(originHtlc))
sender.send(alice, cmd)
sender.expectMsg("ok")
val htlc = alice2bob.expectMsgType[UpdateAddHtlc]
assert(htlc.id == 1 && htlc.paymentHash == h)
awaitCond(alice.stateData == initialState.copy(
commitments = initialState.commitments.copy(localCurrentHtlcId = 1, localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil))))
val binding = relayer.expectMsgType[Binding]
assert(binding === Binding(downstream = originHtlc, upstream = htlc))
relayer.expectMsg(Binding(htlc, origin = Relayed(originHtlc)))
}
}

View file

@ -115,7 +115,7 @@ class RelayerSpec extends TestkitBaseClass {
val cmd_bc = channel_bc.expectMsgType[CMD_ADD_HTLC]
paymentHandler.expectNoMsg(1 second)
assert(cmd_bc.origin === Some(add_ab))
assert(cmd_bc.origin === Relayed(add_ab))
}
@ -177,7 +177,7 @@ class RelayerSpec extends TestkitBaseClass {
sender.send(relayer, 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)
sender.send(relayer, Binding(add_ab, add_bc))
sender.send(relayer, Binding(add_bc, Relayed(add_ab)))
// preimage is wrong, does not matter here
val fulfill_cb = UpdateFulfillHtlc(channelId = add_bc.channelId, id = add_bc.id, paymentPreimage = "00" * 32)
sender.send(relayer, (add_bc, fulfill_cb))
@ -203,7 +203,7 @@ class RelayerSpec extends TestkitBaseClass {
// and then manually build an htlc
UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.expiry, cmd.paymentHash, cmd.onion)
}
sender.send(relayer, Binding(add_ab, Local))
// preimage is wrong, does not matter here
val fulfill_cb = UpdateFulfillHtlc(channelId = add_ab.channelId, id = add_ab.id, paymentPreimage = "00" * 32)
sender.send(relayer, (add_ab, fulfill_cb))
@ -229,7 +229,7 @@ class RelayerSpec extends TestkitBaseClass {
sender.send(relayer, 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)
sender.send(relayer, Binding(add_ab, add_bc))
sender.send(relayer, Binding(add_bc, Relayed(add_ab)))
val fail_cb = UpdateFailHtlc(channelId = add_bc.channelId, id = add_bc.id, reason = "some reason".getBytes())
sender.send(relayer, (add_bc, fail_cb))
@ -254,7 +254,7 @@ class RelayerSpec extends TestkitBaseClass {
// and then manually build an htlc
UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.expiry, cmd.paymentHash, cmd.onion)
}
sender.send(relayer, Binding(add_ab, Local))
val fail_cb = UpdateFailHtlc(channelId = add_ab.channelId, id = add_ab.id, reason = "some reason".getBytes())
sender.send(relayer, (add_ab, fail_cb))

View file

@ -0,0 +1,40 @@
package fr.acinq.eclair.router
import fr.acinq.eclair.router.Announcements._
import fr.acinq.eclair.{Globals, _}
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import scala.compat.Platform
/**
* Created by PM on 31/05/2016.
*/
@RunWith(classOf[JUnitRunner])
class AnnouncementsSpec extends FunSuite {
test("create valid signed channel announcement") {
val (node_a, node_b, bitcoin_a, bitcoin_b) = (randomKey, randomKey, randomKey, randomKey)
val (node_a_sig, bitcoin_a_sig) = signChannelAnnouncement(42, node_a, node_b.publicKey, bitcoin_a, bitcoin_b.publicKey)
val (node_b_sig, bitcoin_b_sig) = signChannelAnnouncement(42, node_b, node_a.publicKey, bitcoin_b, bitcoin_a.publicKey)
val ann = makeChannelAnnouncement(42, node_a.publicKey, node_b.publicKey, bitcoin_a.publicKey, bitcoin_b.publicKey, node_a_sig, node_b_sig, bitcoin_a_sig, bitcoin_b_sig)
assert(checkSigs(ann))
assert(checkSigs(ann.copy(nodeId1 = randomKey.publicKey)) === false)
}
test("create valid signed node announcement") {
val key = randomKey
val ann = makeNodeAnnouncement(key, Globals.Node.alias, Globals.Node.color, Globals.Node.address :: Nil, Platform.currentTime / 1000)
assert(checkSig(ann))
assert(checkSig(ann.copy(timestamp = 153)) === false)
}
test("create valid signed channel update announcement") {
val key = randomKey
val ann = makeChannelUpdate(key, randomKey.publicKey, 45561, Globals.expiry_delta_blocks, Globals.htlc_minimum_msat, Globals.fee_base_msat, Globals.fee_proportional_millionth, Platform.currentTime / 1000)
assert(checkSig(ann, key.publicKey))
assert(checkSig(ann, randomKey.publicKey) === false)
}
}

View file

@ -2,7 +2,7 @@ package fr.acinq.eclair.router
import akka.actor.ActorRef
import akka.testkit.TestProbe
import fr.acinq.eclair.router.Router.DUMMY_SIG
import fr.acinq.bitcoin.BinaryData
import fr.acinq.eclair.wire._
import fr.acinq.eclair.{TestkitBaseClass, randomKey}
import org.junit.runner.RunWith
@ -24,6 +24,8 @@ abstract class BaseRouterSpec extends TestkitBaseClass {
val (a, b, c, d, e, f) = (randomPubkey, randomPubkey, randomPubkey, randomPubkey, randomPubkey, randomPubkey)
val DUMMY_SIG = BinaryData("3045022100e0a180fdd0fe38037cc878c03832861b40a29d32bd7b40b10c9e1efc8c1468a002205ae06d1624896d0d29f4b31e32772ea3cb1b4d7ed4e077e5da28dcc33c0e781201")
val ann_a = NodeAnnouncement(DUMMY_SIG, 0, a, (0, 0, 0), "node-A", "0000", Nil)
val ann_b = NodeAnnouncement(DUMMY_SIG, 0, b, (0, 0, 0), "node-B", "0000", Nil)
val ann_c = NodeAnnouncement(DUMMY_SIG, 0, c, (0, 0, 0), "node-C", "0000", Nil)
@ -31,16 +33,16 @@ abstract class BaseRouterSpec extends TestkitBaseClass {
val ann_e = NodeAnnouncement(DUMMY_SIG, 0, e, (0, 0, 0), "node-E", "0000", Nil)
val ann_f = NodeAnnouncement(DUMMY_SIG, 0, f, (0, 0, 0), "node-F", "0000", Nil)
val chan_ab = ChannelAnnouncement(DUMMY_SIG, DUMMY_SIG, channelId = 1, DUMMY_SIG, DUMMY_SIG, a, b, "", "")
val chan_bc = ChannelAnnouncement(DUMMY_SIG, DUMMY_SIG, channelId = 2, DUMMY_SIG, DUMMY_SIG, b, c, "", "")
val chan_cd = ChannelAnnouncement(DUMMY_SIG, DUMMY_SIG, channelId = 3, DUMMY_SIG, DUMMY_SIG, c, d, "", "")
val chan_ef = ChannelAnnouncement(DUMMY_SIG, DUMMY_SIG, channelId = 4, DUMMY_SIG, DUMMY_SIG, e, f, "", "")
val chan_ab = ChannelAnnouncement(DUMMY_SIG, DUMMY_SIG, DUMMY_SIG, DUMMY_SIG, channelId = 1, a, b, "", "")
val chan_bc = ChannelAnnouncement(DUMMY_SIG, DUMMY_SIG, DUMMY_SIG, DUMMY_SIG, channelId = 2, b, c, "", "")
val chan_cd = ChannelAnnouncement(DUMMY_SIG, DUMMY_SIG, DUMMY_SIG, DUMMY_SIG, channelId = 3, c, d, "", "")
val chan_ef = ChannelAnnouncement(DUMMY_SIG, DUMMY_SIG, DUMMY_SIG, DUMMY_SIG, channelId = 4, e, f, "", "")
val defaultChannelUpdate = ChannelUpdate(Router.DUMMY_SIG, 0, 0, "0000", 0, 0, 0, 0)
val channelUpdate_ab = ChannelUpdate(Router.DUMMY_SIG, channelId = 1, 0, "0000", cltvExpiryDelta = 7, 0, feeBaseMsat = 766000, feeProportionalMillionths = 10)
val channelUpdate_bc = ChannelUpdate(Router.DUMMY_SIG, channelId = 2, 0, "0000", cltvExpiryDelta = 5, 0, feeBaseMsat = 233000, feeProportionalMillionths = 1)
val channelUpdate_cd = ChannelUpdate(Router.DUMMY_SIG, channelId = 3, 0, "0000", cltvExpiryDelta = 3, 0, feeBaseMsat = 153000, feeProportionalMillionths = 4)
val channelUpdate_ef = ChannelUpdate(Router.DUMMY_SIG, channelId = 4, 0, "0000", cltvExpiryDelta = 9, 0, feeBaseMsat = 786000, feeProportionalMillionths = 8)
val defaultChannelUpdate = ChannelUpdate(DUMMY_SIG, 0, 0, "0000", 0, 0, 0, 0)
val channelUpdate_ab = ChannelUpdate(DUMMY_SIG, channelId = 1, 0, "0000", cltvExpiryDelta = 7, 0, feeBaseMsat = 766000, feeProportionalMillionths = 10)
val channelUpdate_bc = ChannelUpdate(DUMMY_SIG, channelId = 2, 0, "0000", cltvExpiryDelta = 5, 0, feeBaseMsat = 233000, feeProportionalMillionths = 1)
val channelUpdate_cd = ChannelUpdate(DUMMY_SIG, channelId = 3, 0, "0000", cltvExpiryDelta = 3, 0, feeBaseMsat = 153000, feeProportionalMillionths = 4)
val channelUpdate_ef = ChannelUpdate(DUMMY_SIG, channelId = 4, 0, "0000", cltvExpiryDelta = 9, 0, feeBaseMsat = 786000, feeProportionalMillionths = 8)
override def withFixture(test: OneArgTest) = {
@ -49,7 +51,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, ann_a))
val router = system.actorOf(Router.props(watcher.ref))
// we announce channels
router ! chan_ab
router ! chan_bc

View file

@ -2,11 +2,13 @@ package fr.acinq.eclair.router
import fr.acinq.bitcoin.Crypto.PrivateKey
import fr.acinq.bitcoin.{BinaryData, Crypto}
import fr.acinq.eclair.{Globals, _}
import fr.acinq.eclair.wire.ChannelUpdate
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import scala.compat.Platform
import scala.concurrent.Await
import scala.concurrent.duration._
@ -98,14 +100,16 @@ class RouteCalculationSpec extends FunSuite {
test("calculate route and return metadata") {
val uab = ChannelUpdate(Router.DUMMY_SIG, 1L, 0L, "0000", 1, 42, 2500, 140)
val uba = ChannelUpdate(Router.DUMMY_SIG, 1L, 1L, "0001", 1, 43, 2501, 141)
val ubc = ChannelUpdate(Router.DUMMY_SIG, 2L, 1L, "0000", 1, 44, 2502, 142)
val ucb = ChannelUpdate(Router.DUMMY_SIG, 2L, 1L, "0001", 1, 45, 2503, 143)
val ucd = ChannelUpdate(Router.DUMMY_SIG, 3L, 1L, "0000", 1, 46, 2504, 144)
val udc = ChannelUpdate(Router.DUMMY_SIG, 3L, 1L, "0001", 1, 47, 2505, 145)
val ude = ChannelUpdate(Router.DUMMY_SIG, 4L, 1L, "0000", 1, 48, 2506, 146)
val ued = ChannelUpdate(Router.DUMMY_SIG, 4L, 1L, "0001", 1, 49, 2507, 147)
val DUMMY_SIG = BinaryData("3045022100e0a180fdd0fe38037cc878c03832861b40a29d32bd7b40b10c9e1efc8c1468a002205ae06d1624896d0d29f4b31e32772ea3cb1b4d7ed4e077e5da28dcc33c0e781201")
val uab = ChannelUpdate(DUMMY_SIG, 1L, 0L, "0000", 1, 42, 2500, 140)
val uba = ChannelUpdate(DUMMY_SIG, 1L, 1L, "0001", 1, 43, 2501, 141)
val ubc = ChannelUpdate(DUMMY_SIG, 2L, 1L, "0000", 1, 44, 2502, 142)
val ucb = ChannelUpdate(DUMMY_SIG, 2L, 1L, "0001", 1, 45, 2503, 143)
val ucd = ChannelUpdate(DUMMY_SIG, 3L, 1L, "0000", 1, 46, 2504, 144)
val udc = ChannelUpdate(DUMMY_SIG, 3L, 1L, "0001", 1, 47, 2505, 145)
val ude = ChannelUpdate(DUMMY_SIG, 4L, 1L, "0000", 1, 48, 2506, 146)
val ued = ChannelUpdate(DUMMY_SIG, 4L, 1L, "0001", 1, 49, 2507, 147)
val updates = Map(
ChannelDesc(1L, a, b) -> uab,

View file

@ -150,7 +150,7 @@ class CodecsSpec extends FunSuite {
val update_fail_htlc = UpdateFailHtlc(1, 2, bin(154, 0))
val commit_sig = CommitSig(1, randomSignature, randomSignature :: randomSignature :: randomSignature :: Nil)
val revoke_and_ack = RevokeAndAck(1, scalar(0), point(1), randomSignature :: randomSignature :: randomSignature :: randomSignature :: randomSignature :: Nil)
val channel_announcement = ChannelAnnouncement(randomSignature, randomSignature, 1, randomSignature, randomSignature, bin(33, 5), bin(33, 6), bin(33, 7), bin(33, 8))
val channel_announcement = ChannelAnnouncement(randomSignature, randomSignature, randomSignature, randomSignature, 1, bin(33, 5), bin(33, 6), bin(33, 7), bin(33, 8))
val node_announcement = NodeAnnouncement(randomSignature, 1, bin(33, 2), (100.toByte, 200.toByte, 300.toByte), "node-alias", bin(0, 0), new InetSocketAddress(InetAddress.getByAddress(Array[Byte](192.toByte, 168.toByte, 1.toByte, 42.toByte)), 42000) :: Nil)
val channel_update = ChannelUpdate(randomSignature, 1, 2, bin(2, 2), 3, 4, 5, 6)