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:
parent
a33f2dc703
commit
92d297b3d9
18 changed files with 297 additions and 188 deletions
|
@ -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 {
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue