Compare commits

...

4 Commits

Author SHA1 Message Date
pm47
00894bae64 moved akka conf to eclair-node application.conf 2017-11-24 16:15:12 +01:00
Fabrice Drouin
4a4640bc86 Bitcoin rpc client: queue requests locally (#223)
use a local queue for outgoing rpc requests. this should be a better solution than
inceasing the number of concurrent requests.
see https://doc.akka.io/docs/akka-http/current/scala/http/client-side/host-level.html#using-the-host-level-api-with-a-queue
for more information.
2017-11-22 14:48:23 +01:00
Pierre-Marie Padiou
bfa3e1c2ca Reformat + optimized imports (#222)
* Reformat + optimized imports

* Fixed unwanted modifications
2017-11-21 20:08:15 +01:00
Pierre-Marie Padiou
df67157119 fix doc for api call connect/open (#220)
Fixes #215.
2017-11-21 19:11:07 +01:00
111 changed files with 639 additions and 480 deletions

View File

@ -118,8 +118,8 @@ java -Declair.datadir=/tmp/node1 -jar eclair-node-gui-<version>-<commit_id>.jar
method | params | description
-------------|-----------------------------------------------|-----------------------------------------------------------
getinfo | | return basic node information (id, chain hash, current block height)
connect | host, port, nodeId | connect to another lightning node through a secure connection
open | host, port, nodeId, fundingSatoshis, pushMsat | opens a channel with another lightning node
connect | nodeId, host, port | connect to another lightning node through a secure connection
open | nodeId, host, port, fundingSatoshis, pushMsat | opens a channel with another lightning node
peers | | list existing local peers
channels | | list existing local channels
channel | channelId | retrieve detailed information about a given channel

View File

@ -83,9 +83,9 @@
<profile>
<id>Mac</id>
<activation>
<os>
<family>mac</family>
</os>
<os>
<family>mac</family>
</os>
</activation>
<properties>
<bitcoind.url>https://bitcoin.org/bin/bitcoin-core-0.14.0/bitcoin-0.14.0-osx64.tar.gz

View File

@ -1,4 +1,10 @@
{
"127.0.0.1": {"t":"51001", "s":"51002"},
"10.0.2.2": {"t":"51001", "s":"51002"}
"127.0.0.1": {
"t": "51001",
"s": "51002"
},
"10.0.2.2": {
"t": "51001",
"s": "51002"
}
}

View File

@ -1,5 +1,14 @@
{
"testnetnode.arihanc.com": {"t":"51001", "s":"51002"},
"testnet.hsmiths.com": {"t":"53011", "s":"53012"},
"electrum.akinbo.org": {"t":"51001", "s":"51002"}
"testnetnode.arihanc.com": {
"t": "51001",
"s": "51002"
},
"testnet.hsmiths.com": {
"t": "53011",
"s": "53012"
},
"electrum.akinbo.org": {
"t": "51001",
"s": "51002"
}
}

View File

@ -81,21 +81,4 @@ eclair {
auto-reconnect = true
payment-handler = "local"
}
akka {
loggers = ["akka.event.slf4j.Slf4jLogger"]
loglevel = "DEBUG"
actor {
debug {
# enable DEBUG logging of all LoggingFSMs for events, transitions and timers
fsm = on
}
}
http {
host-connection-pool {
max-open-requests = 64
}
}
}

View File

@ -8,8 +8,11 @@ import akka.event.{DiagnosticLoggingAdapter, LoggingAdapter}
* See https://groups.google.com/forum/#!topic/akka-user/0CxR8CImr4Q
*/
trait FSMDiagnosticActorLogging[S, D] extends FSM[S, D] {
import akka.event.Logging._
val diagLog: DiagnosticLoggingAdapter = akka.event.Logging(this)
def mdc(currentMessage: Any): MDC = emptyMDC
override def log: LoggingAdapter = diagLog

View File

@ -2,7 +2,6 @@ package fr.acinq.eclair
import java.util.BitSet
import java.util.function.IntPredicate
import fr.acinq.bitcoin.BinaryData
@ -20,14 +19,14 @@ object Features {
* @param features feature bits
* @return true if an initial dump of the routing table is requested
*/
def initialRoutingSync(features: BitSet) : Boolean = features.get(INITIAL_ROUTING_SYNC_BIT_OPTIONAL)
def initialRoutingSync(features: BitSet): Boolean = features.get(INITIAL_ROUTING_SYNC_BIT_OPTIONAL)
/**
*
* @param features feature bits
* @return true if an initial dump of the routing table is requested
*/
def initialRoutingSync(features: BinaryData) : Boolean = initialRoutingSync(BitSet.valueOf(features.reverse.toArray))
def initialRoutingSync(features: BinaryData): Boolean = initialRoutingSync(BitSet.valueOf(features.reverse.toArray))
/**
* Check that the features that we understand are correctly specified, and that there are no mandatory features that
@ -35,7 +34,7 @@ object Features {
*/
def areSupported(bitset: BitSet): Boolean = {
// for now there is no mandatory feature bit, so we don't support features with any even bit set
for(i <- 0 until bitset.length() by 2) {
for (i <- 0 until bitset.length() by 2) {
if (bitset.get(i)) return false
}
return true

View File

@ -57,8 +57,11 @@ case class NodeParams(extendedPrivateKey: ExtendedPrivateKey,
object NodeParams {
sealed trait WatcherType
object BITCOIND extends WatcherType
object BITCOINJ extends WatcherType
object ELECTRUM extends WatcherType
/**

View File

@ -93,7 +93,7 @@ class Setup(datadir: File, overrideDefaults: Config = ConfigFactory.empty(), act
}
val stream = classOf[Setup].getResourceAsStream(addressesFile)
val addresses = ElectrumClient.readServerAddresses(stream)
val electrumClient = system.actorOf(SimpleSupervisor.props(Props(new ElectrumClient(addresses)), "electrum-client", SupervisorStrategy.Resume))
val electrumClient = system.actorOf(SimpleSupervisor.props(Props(new ElectrumClient(addresses)), "electrum-client", SupervisorStrategy.Resume))
Electrum(electrumClient)
}
@ -190,10 +190,13 @@ class Setup(datadir: File, overrideDefaults: Config = ConfigFactory.empty(), act
}
// @formatter:off
sealed trait Bitcoin
case class Bitcoind(extendedBitcoinClient: ExtendedBitcoinClient) extends Bitcoin
case class Bitcoinj(bitcoinjKit: BitcoinjKit) extends Bitcoin
case class Electrum(electrumClient: ActorRef) extends Bitcoin
// @formatter:on
case class Kit(nodeParams: NodeParams,
system: ActorSystem,

View File

@ -31,4 +31,5 @@ object UInt64 {
implicit def longToUint64(l: Long) = UInt64(l)
}
}

View File

@ -1,7 +1,7 @@
package fr.acinq.eclair.api
import fr.acinq.bitcoin.{BinaryData, Script, ScriptElt, Transaction}
import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, PublicKey, Scalar}
import fr.acinq.bitcoin.{BinaryData, Transaction}
import fr.acinq.eclair.channel.State
import fr.acinq.eclair.crypto.ShaChain
import fr.acinq.eclair.transactions.Transactions.TransactionWithInputInfo

View File

@ -77,7 +77,7 @@ trait Service extends Logging {
import kit._
val f_res: Future[AnyRef] = req match {
case JsonRPCBody(_, _, "getinfo", _) => getInfoResponse
case JsonRPCBody(_, _, "connect", JString(host) :: JInt(port) :: JString(nodeId) :: Nil) =>
case JsonRPCBody(_, _, "connect", JString(nodeId) :: JString(host) :: JInt(port) :: Nil) =>
(switchboard ? NewConnection(PublicKey(nodeId), new InetSocketAddress(host, port.toInt), None)).mapTo[String]
case JsonRPCBody(_, _, "open", JString(nodeId) :: JString(host) :: JInt(port) :: JInt(fundingSatoshi) :: JInt(pushMsat) :: options) =>
val channelFlags = options match {
@ -95,7 +95,7 @@ trait Service extends Logging {
(router ? 'nodes).mapTo[Iterable[NodeAnnouncement]].map(_.map(_.nodeId))
case JsonRPCBody(_, _, "allchannels", _) =>
(router ? 'channels).mapTo[Iterable[ChannelAnnouncement]].map(_.map(c => ChannelInfo(c.shortChannelId.toHexString, c.nodeId1, c.nodeId2)))
case JsonRPCBody(_,_, "receive", JInt(amountMsat) :: JString(description) :: Nil) =>
case JsonRPCBody(_, _, "receive", JInt(amountMsat) :: JString(description) :: Nil) =>
(paymentHandler ? ReceivePayment(MilliSatoshi(amountMsat.toLong), description)).mapTo[PaymentRequest].map(PaymentRequest.write)
case JsonRPCBody(_, _, "send", JInt(amountMsat) :: JString(paymentHash) :: JString(nodeId) :: Nil) =>
(paymentInitiator ? SendPayment(amountMsat.toLong, paymentHash, PublicKey(nodeId))).mapTo[PaymentResult]
@ -120,7 +120,7 @@ trait Service extends Logging {
getChannel(channelId).flatMap(_ ? CMD_CLOSE(scriptPubKey = None)).mapTo[String]
case JsonRPCBody(_, _, "help", _) =>
Future.successful(List(
"connect (host, port, nodeId): connect to another lightning node through a secure connection",
"connect (nodeId, host, port): connect to another lightning node through a secure connection",
"open (nodeId, host, port, fundingSatoshi, pushMsat, channelFlags = 0x01): open a channel with another lightning node",
"peers: list existing local peers",
"channels: list existing local channels",

View File

@ -30,6 +30,7 @@ trait EclairWallet {
/**
* Cancels this transaction: this probably translates to "release locks on utxos".
*
* @param tx
* @return
*/

View File

@ -15,7 +15,7 @@ import scala.concurrent.{ExecutionContext, Future, Promise}
/**
* Due to bitcoin-core wallet not fully supporting segwit txes yet, our current scheme is:
* utxos <- parent-tx <- funding-tx
* utxos <- parent-tx <- funding-tx
*
* With:
* - utxos may be non-segwit
@ -200,6 +200,7 @@ class BitcoinCoreWallet(rpcClient: BitcoinJsonRPCClient, watcher: ActorRef)(impl
/**
* We currently only put a lock on the parent tx inputs, and we publish the parent tx immediately so there is nothing
* to do here.
*
* @param tx
* @return
*/

View File

@ -8,12 +8,15 @@ import akka.http.scaladsl.marshalling.Marshal
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials}
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.stream.ActorMaterializer
import akka.stream.{ActorMaterializer, OverflowStrategy, QueueOfferResult}
import akka.stream.scaladsl.{Keep, Sink, Source}
import de.heikoseeberger.akkahttpjson4s.Json4sSupport._
import org.json4s.JsonAST.JValue
import org.json4s.{DefaultFormats, jackson}
import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
// @formatter:off
case class JsonRPCRequest(jsonrpc: String = "1.0", id: String = "scala-client", method: String, params: Seq[Any])
@ -26,16 +29,35 @@ class BitcoinJsonRPCClient(user: String, password: String, host: String = "127.0
val scheme = if (ssl) "https" else "http"
val uri = Uri(s"$scheme://$host:$port")
implicit val materializer = ActorMaterializer()
val httpClient = Http(system)
implicit val serialization = jackson.Serialization
implicit val formats = DefaultFormats
implicit val materializer = ActorMaterializer()
val httpClientFlow = Http().cachedHostConnectionPool[Promise[HttpResponse]](host, port)
val queueSize = 512
val queue = Source.queue[(HttpRequest, Promise[HttpResponse])](queueSize, OverflowStrategy.dropNew)
.via(httpClientFlow)
.toMat(Sink.foreach({
case ((Success(resp), p)) => p.success(resp)
case ((Failure(e), p)) => p.failure(e)
}))(Keep.left)
.run()
def queueRequest(request: HttpRequest): Future[HttpResponse] = {
val responsePromise = Promise[HttpResponse]()
queue.offer(request -> responsePromise).flatMap {
case QueueOfferResult.Enqueued => responsePromise.future
case QueueOfferResult.Dropped => Future.failed(new RuntimeException("Queue overflowed. Try again later."))
case QueueOfferResult.Failure(ex) => Future.failed(ex)
case QueueOfferResult.QueueClosed => Future.failed(new RuntimeException("Queue was closed (pool shut down) while running the request. Try again later."))
}
}
def invoke(method: String, params: Any*)(implicit ec: ExecutionContext): Future[JValue] =
for {
entity <- Marshal(JsonRPCRequest(method = method, params = params)).to[RequestEntity]
httpRes <- httpClient.singleRequest(HttpRequest(uri = uri, method = HttpMethods.POST).addHeader(Authorization(BasicHttpCredentials(user, password))).withEntity(entity))
httpRes <- queueRequest(HttpRequest(uri = "/", method = HttpMethods.POST).addHeader(Authorization(BasicHttpCredentials(user, password))).withEntity(entity))
jsonRpcRes <- Unmarshal(httpRes).to[JsonRPCResponse].map {
case JsonRPCResponse(_, Some(error), _) => throw JsonRPCError(error)
case o => o
@ -47,7 +69,7 @@ class BitcoinJsonRPCClient(user: String, password: String, host: String = "127.0
def invoke(request: Seq[(String, Seq[Any])])(implicit ec: ExecutionContext): Future[Seq[JValue]] =
for {
entity <- Marshal(request.map(r => JsonRPCRequest(method = r._1, params = r._2))).to[RequestEntity]
httpRes <- httpClient.singleRequest(HttpRequest(uri = uri, method = HttpMethods.POST).addHeader(Authorization(BasicHttpCredentials(user, password))).withEntity(entity))
httpRes <- queueRequest(HttpRequest(uri = "/", method = HttpMethods.POST).addHeader(Authorization(BasicHttpCredentials(user, password))).withEntity(entity))
jsonRpcRes <- Unmarshal(httpRes).to[Seq[JsonRPCResponse]].map {
//case JsonRPCResponse(_, Some(error), _) => throw JsonRPCError(error)
case o => o

View File

@ -7,8 +7,8 @@ import akka.actor.ActorSystem
import com.google.common.util.concurrent.{FutureCallback, Futures}
import fr.acinq.bitcoin.Transaction
import fr.acinq.eclair.Globals
import fr.acinq.eclair.blockchain.bitcoinj.BitcoinjKit._
import fr.acinq.eclair.blockchain.CurrentBlockCount
import fr.acinq.eclair.blockchain.bitcoinj.BitcoinjKit._
import grizzled.slf4j.Logging
import org.bitcoinj.core.TransactionConfidence.ConfidenceType
import org.bitcoinj.core.listeners._
@ -41,8 +41,8 @@ class BitcoinjKit(chain: String, datadir: File, staticPeers: List[InetSocketAddr
val atCurrentHeight = atCurrentHeightPromise.future
// tells us when we are at current block height
// private val syncedPromise = Promise[Boolean]()
// val synced = syncedPromise.future
// private val syncedPromise = Promise[Boolean]()
// val synced = syncedPromise.future
private def updateBlockCount(blockCount: Int) = {
// when synchronizing we don't want to advertise previous blocks
@ -61,13 +61,13 @@ class BitcoinjKit(chain: String, datadir: File, staticPeers: List[InetSocketAddr
peerGroup().setMinRequiredProtocolVersion(70015) // bitcoin core 0.13
wallet().watchMode = true
// setDownloadListener(new DownloadProgressTracker {
// override def doneDownload(): Unit = {
// super.doneDownload()
// // may be called multiple times
// syncedPromise.trySuccess(true)
// }
// })
// setDownloadListener(new DownloadProgressTracker {
// override def doneDownload(): Unit = {
// super.doneDownload()
// // may be called multiple times
// syncedPromise.trySuccess(true)
// }
// })
// we set the blockcount to the previous stored block height
updateBlockCount(chain().getBestChainHeight)

View File

@ -16,7 +16,6 @@ import org.bitcoinj.script.Script
import scala.collection.SortedMap
import scala.concurrent.ExecutionContext
import scala.concurrent.duration._
import scala.util.{Failure, Success, Try}
final case class NewConfidenceLevel(tx: Transaction, blockHeight: Int, confirmations: Int) extends BlockchainEvent
@ -130,6 +129,7 @@ class BitcoinjWatcher(val kit: WalletAppKit)(implicit ec: ExecutionContext = Exe
/**
* Bitcoinj needs hints to be able to detect transactions
*
* @param pubkeyScript
* @return
*/
@ -177,6 +177,7 @@ class Broadcaster(kit: WalletAppKit) extends Actor with ActorLogging {
}
case class BroadcastResult(tx: Transaction, result: Try[Boolean])
def broadcast(tx: Transaction) = {
Context.propagate(kit.wallet().getContext)
val bitcoinjTx = new org.bitcoinj.core.Transaction(kit.wallet().getParams, Transaction.write(tx))
@ -189,5 +190,4 @@ class Broadcaster(kit: WalletAppKit) extends Actor with ActorLogging {
}
}

View File

@ -10,7 +10,7 @@ import grizzled.slf4j.Logging
import scala.concurrent.{ExecutionContext, Future}
class ElectrumEclairWallet(val wallet: ActorRef)(implicit system: ActorSystem, ec: ExecutionContext, timeout: akka.util.Timeout) extends EclairWallet with Logging {
class ElectrumEclairWallet(val wallet: ActorRef)(implicit system: ActorSystem, ec: ExecutionContext, timeout: akka.util.Timeout) extends EclairWallet with Logging {
override def getBalance = (wallet ? GetBalance).mapTo[GetBalanceResponse].map(balance => balance.confirmed + balance.unconfirmed)
@ -45,7 +45,7 @@ class ElectrumEclairWallet(val wallet: ActorRef)(implicit system: ActorSystem, e
case CancelTransactionResponse(_) => false
}
def sendPayment(amount: Satoshi, address: String, feeRatePerKw: Long) : Future[String] = {
def sendPayment(amount: Satoshi, address: String, feeRatePerKw: Long): Future[String] = {
val publicKeyScript = Base58Check.decode(address) match {
case (Base58.Prefix.PubkeyAddressTestnet, pubKeyHash) => Script.pay2pkh(pubKeyHash)
case (Base58.Prefix.ScriptAddressTestnet, scriptHash) => OP_HASH160 :: OP_PUSHDATA(scriptHash) :: OP_EQUAL :: Nil

View File

@ -342,6 +342,7 @@ object ElectrumWallet {
/**
* use BIP49 (and not BIP44) since we use p2sh-of-p2wpkh
*
* @param master master key
* @return the BIP49 account key for this master key: m/49'/1'/0'/0
*/
@ -349,6 +350,7 @@ object ElectrumWallet {
/**
* use BIP49 (and not BIP44) since we use p2sh-of-p2wpkh
*
* @param master master key
* @return the BIP49 change key for this master key: m/49'/1'/0'/1
*/

View File

@ -1,4 +1,5 @@
package fr.acinq.eclair.blockchain.fee
import scala.concurrent.Future
/**

View File

@ -28,6 +28,7 @@ object FeeratesPerKw {
/**
* Used in tests
*
* @param feeratePerKw
* @return
*/

View File

@ -7,7 +7,6 @@ import fr.acinq.bitcoin.Crypto.{PublicKey, ripemd160, sha256}
import fr.acinq.bitcoin._
import fr.acinq.eclair.NodeParams.BITCOINJ
import fr.acinq.eclair._
import fr.acinq.eclair.blockchain.WatchConfirmed.extractPublicKeyScript
import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.channel.Helpers.{Closing, Funding}
import fr.acinq.eclair.crypto.{Generators, ShaChain, Sphinx}

View File

@ -8,7 +8,7 @@ import fr.acinq.eclair.UInt64
*/
class ChannelException(channelId: BinaryData, message: String) extends RuntimeException(message)
// @formatter:off
case class DebugTriggeredException (channelId: BinaryData) extends ChannelException(channelId, "debug-mode triggered failure")
case class ChannelReserveTooHigh (channelId: BinaryData, channelReserveSatoshis: Long, reserveToFundingRatio: Double, maxReserveToFundingRatio: Double) extends ChannelException(channelId, s"channelReserveSatoshis too high: reserve=$channelReserveSatoshis fundingRatio=$reserveToFundingRatio maxFundingRatio=$maxReserveToFundingRatio")
case class ClosingInProgress (channelId: BinaryData) extends ChannelException(channelId, "cannot send new htlcs, closing in progress")
@ -39,4 +39,5 @@ case class UnexpectedRevocation (channelId: BinaryData) extends C
case class InvalidRevocation (channelId: BinaryData) extends ChannelException(channelId, "invalid revocation")
case class CommitmentSyncError (channelId: BinaryData) extends ChannelException(channelId, "commitment sync error")
case class RevocationSyncError (channelId: BinaryData) extends ChannelException(channelId, "revocation sync error")
case class InvalidFailureCode (channelId: BinaryData) extends ChannelException(channelId, "UpdateFailMalformedHtlc message doesn't have BADONION bit set")
case class InvalidFailureCode (channelId: BinaryData) extends ChannelException(channelId, "UpdateFailMalformedHtlc message doesn't have BADONION bit set")
// @formatter:on

View File

@ -1,7 +1,7 @@
package fr.acinq.eclair.channel
import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, sha256}
import fr.acinq.bitcoin.{BinaryData, Crypto, Satoshi, Script, Transaction}
import fr.acinq.bitcoin.{BinaryData, Crypto, Satoshi, Transaction}
import fr.acinq.eclair.crypto.{Generators, ShaChain, Sphinx}
import fr.acinq.eclair.payment.Origin
import fr.acinq.eclair.transactions.Transactions._

View File

@ -2,7 +2,7 @@ package fr.acinq.eclair.channel
import akka.actor.{Actor, ActorLogging, ActorRef}
import fr.acinq.eclair.NodeParams
import fr.acinq.eclair.wire.{Error, LightningMessage}
import fr.acinq.eclair.wire.LightningMessage
/**
* Created by fabrice on 27/02/17.

View File

@ -490,6 +490,7 @@ object Helpers {
* A local commit is considered done when:
* - all commitment tx outputs that we can spend have been spent and confirmed (even if the spending tx was not ours)
* - all 3rd stage txes (txes spending htlc txes) have been confirmed
*
* @param localCommitPublished
* @return
*/
@ -509,6 +510,7 @@ object Helpers {
/**
* A remote commit is considered done when all commitment tx outputs that we can spend have been spent and confirmed
* (even if the spending tx was not ours).
*
* @param remoteCommitPublished
* @return
*/
@ -524,6 +526,7 @@ object Helpers {
/**
* A remote commit is considered done when all commitment tx outputs that we can spend have been spent and confirmed
* (even if the spending tx was not ours).
*
* @param revokedCommitPublished
* @return
*/

View File

@ -57,6 +57,7 @@ class Register extends Actor with ActorLogging {
}
object Register {
// @formatter:off
case class Forward[T](channelId: BinaryData, message: T)
case class ForwardShortId[T](shortChannelId: Long, message: T)

View File

@ -1,15 +1,15 @@
package fr.acinq.eclair.crypto
import fr.acinq.bitcoin.BinaryData
import org.spongycastle.util.encoders.Hex
import scala.annotation.tailrec
/**
* Bit stream that can be written to and read at both ends (i.e. you can read from the end or the beginning of the stream)
* @param bytes bits packed as bytes, the last byte is padded with 0s
*
* @param bytes bits packed as bytes, the last byte is padded with 0s
* @param offstart offset at which the first bit is in the first byte
* @param offend offset at which the last bit is in the last byte
* @param offend offset at which the last bit is in the last byte
*/
case class BitStream(bytes: Vector[Byte], offstart: Int, offend: Int) {
@ -20,6 +20,7 @@ case class BitStream(bytes: Vector[Byte], offstart: Int, offend: Int) {
def bitCount = 8 * bytes.length - offstart - offend
def isEmpty = bitCount == 0
/**
* append a byte to a bitstream
*
@ -97,16 +98,17 @@ case class BitStream(bytes: Vector[Byte], offstart: Int, offend: Int) {
BitStream(bytes.dropRight(2) :+ a1.toByte, offstart, offend) -> byte.toByte
}
def popBytes(n: Int) : (BitStream, Seq[Byte]) = {
def popBytes(n: Int): (BitStream, Seq[Byte]) = {
@tailrec
def loop(stream: BitStream, acc: Seq[Byte]) : (BitStream, Seq[Byte]) =
def loop(stream: BitStream, acc: Seq[Byte]): (BitStream, Seq[Byte]) =
if (acc.length == n) (stream, acc) else {
val (stream1, value) = stream.popByte
loop(stream1, acc :+ value)
}
val (stream1, value) = stream.popByte
loop(stream1, acc :+ value)
}
loop(this, Nil)
}
/**
* read the first bit from a bitstream
*
@ -117,7 +119,7 @@ case class BitStream(bytes: Vector[Byte], offstart: Int, offend: Int) {
case _ => BitStream(bytes, offstart + 1, offend) -> firstBit
}
def readBits(count: Int) : (BitStream, Seq[Bit]) = {
def readBits(count: Int): (BitStream, Seq[Bit]) = {
@tailrec
def loop(stream: BitStream, acc: Seq[Bit]): (BitStream, Seq[Bit]) = if (acc.length == count) (stream, acc) else {
val (stream1, bit) = stream.readBit
@ -126,14 +128,15 @@ case class BitStream(bytes: Vector[Byte], offstart: Int, offend: Int) {
loop(this, Nil)
}
/**
* read the first byte from a bitstream
*
* @return
*/
def readByte: (BitStream, Byte) = {
val byte = ((bytes(0) << offstart) | (bytes(1) >>> (7 - offstart))) & 0xff
BitStream(bytes.tail, offstart, offend) -> byte.toByte
val byte = ((bytes(0) << offstart) | (bytes(1) >>> (7 - offstart))) & 0xff
BitStream(bytes.tail, offstart, offend) -> byte.toByte
}
def isSet(pos: Int): Boolean = {

View File

@ -3,8 +3,8 @@ package fr.acinq.eclair.crypto
import java.math.BigInteger
import java.nio.ByteOrder
import fr.acinq.eclair.randomBytes
import fr.acinq.bitcoin.{BinaryData, Crypto, Protocol}
import fr.acinq.eclair.randomBytes
import grizzled.slf4j.Logging
import org.spongycastle.crypto.digests.SHA256Digest
import org.spongycastle.crypto.macs.HMac

View File

@ -97,7 +97,7 @@ object ShaChain {
}
val shaChainCodec: Codec[ShaChain] = {
val shaChainCodec: Codec[ShaChain] = {
import scodec.Codec
import scodec.bits.BitVector
import scodec.codecs._
@ -106,7 +106,7 @@ object ShaChain {
val entryCodec = vectorOfN(uint16, bool) ~ LightningMessageCodecs.varsizebinarydata
// codec for a Map[Vector[Boolean], BinaryData]: write all k ->v pairs using the codec defined above
val mapCodec: Codec[Map[Vector[Boolean], BinaryData]] = Codec[Map[Vector[Boolean], BinaryData]] (
val mapCodec: Codec[Map[Vector[Boolean], BinaryData]] = Codec[Map[Vector[Boolean], BinaryData]](
(m: Map[Vector[Boolean], BinaryData]) => vectorOfN(uint16, entryCodec).encode(m.toVector),
(b: BitVector) => vectorOfN(uint16, entryCodec).decode(b).map(_.map(_.toMap))
)

View File

@ -5,7 +5,7 @@ import java.nio.ByteOrder
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.{BinaryData, Crypto, Protocol}
import fr.acinq.eclair.wire.{ChannelUpdate, FailureMessage, FailureMessageCodecs, LightningMessageCodecs}
import fr.acinq.eclair.wire.{FailureMessage, FailureMessageCodecs}
import grizzled.slf4j.Logging
import org.spongycastle.crypto.digests.SHA256Digest
import org.spongycastle.crypto.macs.HMac
@ -32,7 +32,7 @@ object Sphinx extends Logging {
// onion packet length
val PacketLength = 1 + 33 + MacLength + MaxHops * (PayloadLength + MacLength)
// last packet (all zeroes except for the version byte)
val LAST_PACKET = Packet(Version, zeroes(33), zeroes(MacLength), zeroes(MaxHops * (PayloadLength + MacLength)))

View File

@ -35,6 +35,7 @@ class TransportHandler[T: ClassTag](keyPair: KeyPair, rs: Option[BinaryData], co
connection ! akka.io.Tcp.Register(self)
val out = context.actorOf(Props(new WriteAckSender(connection)))
def buf(message: BinaryData): ByteString = ByteString.fromArray(message)
// it means we initiate the dialog

View File

@ -17,6 +17,7 @@ trait NetworkDb {
/**
* This method removes 1 channel announcement and 2 channel updates (at both ends of the same channel)
*
* @param shortChannelId
* @return
*/

View File

@ -1,7 +1,6 @@
package fr.acinq.eclair.db
import fr.acinq.bitcoin.BinaryData
import fr.acinq.eclair.channel.HasCommitments
/**
* This database stores the preimages that we have received from downstream

View File

@ -1,14 +1,12 @@
package fr.acinq.eclair.db.sqlite
import java.sql.{Connection, ResultSet}
import java.sql.Connection
import fr.acinq.bitcoin.Crypto
import fr.acinq.eclair.db.NetworkDb
import fr.acinq.eclair.router.Announcements
import fr.acinq.eclair.wire.LightningMessageCodecs.{channelAnnouncementCodec, channelUpdateCodec, nodeAnnouncementCodec}
import fr.acinq.eclair.wire.{ChannelAnnouncement, ChannelUpdate, NodeAnnouncement}
import scodec.Codec
import scodec.bits.BitVector
class SqliteNetworkDb(sqlite: Connection) extends NetworkDb {

View File

@ -2,7 +2,7 @@ package fr.acinq.eclair.io
import java.net.InetSocketAddress
import akka.actor.{Actor, ActorLogging, ActorRef, OneForOneStrategy, Props, Status, SupervisorStrategy, Terminated}
import akka.actor.{Actor, ActorLogging, ActorRef, OneForOneStrategy, Props, SupervisorStrategy}
import akka.io.Tcp.SO.KeepAlive
import akka.io.{IO, Tcp}
import fr.acinq.eclair.NodeParams

View File

@ -2,9 +2,9 @@ package fr.acinq.eclair.payment
import akka.actor.{Actor, ActorLogging, Props, Status}
import fr.acinq.bitcoin.{BinaryData, Crypto, MilliSatoshi}
import fr.acinq.eclair.{Globals, NodeParams, randomBytes}
import fr.acinq.eclair.channel.{CMD_FAIL_HTLC, CMD_FULFILL_HTLC, ExpiryTooSmall}
import fr.acinq.eclair.channel.{CMD_FAIL_HTLC, CMD_FULFILL_HTLC}
import fr.acinq.eclair.wire._
import fr.acinq.eclair.{NodeParams, randomBytes}
import scala.util.{Failure, Success, Try}
@ -32,25 +32,25 @@ class LocalPaymentHandler(nodeParams: NodeParams) extends Actor with ActorLoggin
}
case htlc: UpdateAddHtlc =>
if (h2r.contains(htlc.paymentHash)) {
if (h2r.contains(htlc.paymentHash)) {
val r = h2r(htlc.paymentHash)._1
val pr = h2r(htlc.paymentHash)._2
// The htlc amount must be equal or greater than the requested amount. A slight overpaying is permitted, however
// it must not be greater than two times the requested amount.
// see https://github.com/lightningnetwork/lightning-rfc/blob/master/04-onion-routing.md#failure-messages
pr.amount match {
case Some(amount) if MilliSatoshi(htlc.amountMsat) < amount => sender ! CMD_FAIL_HTLC(htlc.id, Right(IncorrectPaymentAmount), commit = true)
case Some(amount) if MilliSatoshi(htlc.amountMsat) > amount * 2 => sender ! CMD_FAIL_HTLC(htlc.id, Right(IncorrectPaymentAmount), commit = true)
case _ =>
log.info(s"received payment for paymentHash=${htlc.paymentHash} amountMsat=${htlc.amountMsat}")
// amount is correct or was not specified in the payment request
sender ! CMD_FULFILL_HTLC(htlc.id, r, commit = true)
context.system.eventStream.publish(PaymentReceived(MilliSatoshi(htlc.amountMsat), htlc.paymentHash))
context.become(run(h2r - htlc.paymentHash))
}
} else {
pr.amount match {
case Some(amount) if MilliSatoshi(htlc.amountMsat) < amount => sender ! CMD_FAIL_HTLC(htlc.id, Right(IncorrectPaymentAmount), commit = true)
case Some(amount) if MilliSatoshi(htlc.amountMsat) > amount * 2 => sender ! CMD_FAIL_HTLC(htlc.id, Right(IncorrectPaymentAmount), commit = true)
case _ =>
log.info(s"received payment for paymentHash=${htlc.paymentHash} amountMsat=${htlc.amountMsat}")
// amount is correct or was not specified in the payment request
sender ! CMD_FULFILL_HTLC(htlc.id, r, commit = true)
context.system.eventStream.publish(PaymentReceived(MilliSatoshi(htlc.amountMsat), htlc.paymentHash))
context.become(run(h2r - htlc.paymentHash))
}
} else {
sender ! CMD_FAIL_HTLC(htlc.id, Right(UnknownPaymentHash), commit = true)
}
}
}
}

View File

@ -18,7 +18,7 @@ object PaymentHop {
/**
*
* @param reversePath sequence of Hops from recipient to a start of assisted path
* @param msat an amount to send to a payment recipient
* @param msat an amount to send to a payment recipient
* @return a sequence of extra hops with a pre-calculated fee for a given msat amount
*/
def buildExtra(reversePath: Seq[Hop], msat: Long): Seq[ExtraHop] = (List.empty[ExtraHop] /: reversePath) {
@ -29,13 +29,18 @@ object PaymentHop {
trait PaymentHop {
def nextFee(msat: Long): Long
def shortChannelId: Long
def cltvExpiryDelta: Int
def nodeId: PublicKey
}
case class Hop(nodeId: PublicKey, nextNodeId: PublicKey, lastUpdate: ChannelUpdate) extends PaymentHop {
def nextFee(msat: Long): Long = PaymentHop.nodeFee(lastUpdate.feeBaseMsat, lastUpdate.feeProportionalMillionths, msat)
def cltvExpiryDelta: Int = lastUpdate.cltvExpiryDelta
def shortChannelId: Long = lastUpdate.shortChannelId
}

View File

@ -1,7 +1,6 @@
package fr.acinq.eclair.payment
import akka.actor.{Actor, ActorLogging, ActorRef, Props}
import fr.acinq.bitcoin.BinaryData
import fr.acinq.bitcoin.Crypto.PublicKey
/**

View File

@ -263,13 +263,13 @@ object PaymentRequest {
case class ExpiryTag(seconds: Long) extends Tag {
override def toInt5s = {
val ints = writeUnsignedLong(seconds)
Bech32.map('x') +: (writeSize(ints.size) ++ ints) }
Bech32.map('x') +: (writeSize(ints.size) ++ ints)
}
}
/**
* Min final CLTV expiry
*
*
* @param blocks min final cltv expiry, in blocks
*/
case class MinFinalCltvExpiryTag(blocks: Long) extends Tag {
@ -418,8 +418,9 @@ object PaymentRequest {
/**
* prepend an unsigned long value to a sequence of Int5s
*
* @param value input value
* @param acc sequence of Int5 values
* @param acc sequence of Int5 values
* @return an update sequence of Int5s
*/
@tailrec
@ -432,10 +433,11 @@ object PaymentRequest {
/**
* convert a tag data size to a sequence of Int5s. It * must * fit on a sequence
* of 2 Int5 values
*
* @param size data size
* @return size as a sequence of exactly 2 Int5 values
*/
def writeSize(size: Long) : Seq[Int5] = {
def writeSize(size: Long): Seq[Int5] = {
val output = writeUnsignedLong(size)
// make sure that size is encoded on 2 int5 values
output.length match {
@ -448,8 +450,9 @@ object PaymentRequest {
/**
* reads an unsigned long value from a sequence of Int5s
*
* @param length length of the sequence
* @param ints sequence of Int5s
* @param ints sequence of Int5s
* @return an unsigned long value
*/
def readUnsignedLong(length: Int, ints: Seq[Int5]): Long = ints.take(length).foldLeft(0L) { case (acc, i) => acc * 32 + i }

View File

@ -79,6 +79,7 @@ object Announcements {
* The creating node MUST set node-id-1 and node-id-2 to the public keys of the
* two nodes who are operating the channel, such that node-id-1 is the numerically-lesser
* of the two DER encoded keys sorted in ascending numerical order,
*
* @return true if localNodeId is node1
*/
def isNode1(localNodeId: BinaryData, remoteNodeId: BinaryData) = LexicographicalOrdering.isLessThan(localNodeId, remoteNodeId)
@ -87,6 +88,7 @@ object Announcements {
* BOLT 7:
* The creating node [...] MUST set the direction bit of flags to 0 if
* the creating node is node-id-1 in that message, otherwise 1.
*
* @return true if the node who sent these flags is node1
*/
def isNode1(flags: BinaryData) = !BitVector(flags.data).reverse.get(0)
@ -94,6 +96,7 @@ object Announcements {
/**
* A node MAY create and send a channel_update with the disable bit set to
* signal the temporary unavailability of a channel
*
* @return
*/
def isEnabled(flags: BinaryData) = !BitVector(flags.data).reverse.get(1)

View File

@ -11,18 +11,18 @@ import fr.acinq.eclair._
import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.channel._
import fr.acinq.eclair.io.Peer
import fr.acinq.eclair.payment.Hop
import fr.acinq.eclair.transactions.Scripts
import fr.acinq.eclair.wire._
import fr.acinq.eclair.payment.Hop
import org.jgrapht.alg.shortestpath.DijkstraShortestPath
import org.jgrapht.ext._
import org.jgrapht.graph.{DefaultDirectedGraph, DefaultEdge, SimpleGraph}
import scala.collection.JavaConversions._
import scala.compat.Platform
import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Random, Success, Try}
import scala.concurrent.duration._
// @formatter:off

View File

@ -7,4 +7,5 @@ package fr.acinq.eclair.router
class RouterException(message: String) extends RuntimeException(message)
object RouteNotFound extends RouterException("Route not found")
object CannotRouteToSelf extends RouterException("Cannot route to self")

View File

@ -13,8 +13,10 @@ import scala.concurrent.duration.{FiniteDuration, _}
*/
class ThrottleForwarder(target: ActorRef, messages: Iterable[Any], chunkSize: Int, delay: FiniteDuration) extends Actor with ActorLogging {
import scala.concurrent.ExecutionContext.Implicits.global
import ThrottleForwarder.Tick
import scala.concurrent.ExecutionContext.Implicits.global
val clock = context.system.scheduler.schedule(0 second, delay, self, Tick)
log.debug(s"sending messages=${messages.size} with chunkSize=$chunkSize and delay=$delay")

View File

@ -1,6 +1,5 @@
package fr.acinq.eclair.transactions
import fr.acinq.bitcoin.BinaryData
import fr.acinq.eclair.wire._
/**

View File

@ -1,8 +1,8 @@
package fr.acinq.eclair.transactions
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey, ripemd160}
import fr.acinq.bitcoin.Crypto.{PublicKey, ripemd160}
import fr.acinq.bitcoin.Script._
import fr.acinq.bitcoin.{BinaryData, Crypto, LexicographicalOrdering, LockTimeThreshold, OP_0, OP_1, OP_1NEGATE, OP_2, OP_2DROP, OP_ADD, OP_CHECKLOCKTIMEVERIFY, OP_CHECKMULTISIG, OP_CHECKSEQUENCEVERIFY, OP_CHECKSIG, OP_DROP, OP_DUP, OP_ELSE, OP_ENDIF, OP_EQUAL, OP_EQUALVERIFY, OP_HASH160, OP_IF, OP_NOTIF, OP_PUSHDATA, OP_SIZE, OP_SWAP, OutPoint, SIGHASH_ALL, Satoshi, Script, ScriptElt, ScriptWitness, Transaction, TxIn, TxOut}
import fr.acinq.bitcoin.{BinaryData, LexicographicalOrdering, LockTimeThreshold, OP_0, OP_1, OP_1NEGATE, OP_2, OP_2DROP, OP_ADD, OP_CHECKLOCKTIMEVERIFY, OP_CHECKMULTISIG, OP_CHECKSEQUENCEVERIFY, OP_CHECKSIG, OP_DROP, OP_DUP, OP_ELSE, OP_ENDIF, OP_EQUAL, OP_EQUALVERIFY, OP_HASH160, OP_IF, OP_NOTIF, OP_PUSHDATA, OP_SIZE, OP_SWAP, Satoshi, Script, ScriptElt, ScriptWitness, Transaction, TxIn}
/**
* Created by PM on 02/12/2016.

View File

@ -1,7 +1,7 @@
package fr.acinq.eclair.wire
import fr.acinq.bitcoin.BinaryData
import fr.acinq.eclair.wire.LightningMessageCodecs.{binarydata, uint64, channelUpdateCodec}
import fr.acinq.eclair.wire.LightningMessageCodecs.{binarydata, channelUpdateCodec, uint64}
import scodec.Codec
import scodec.codecs._

View File

@ -47,9 +47,13 @@ object FixedSizeStrictCodec {
*/
def bytesStrict(size: Int): Codec[ByteVector] = new Codec[ByteVector] {
private val codec = new FixedSizeStrictCodec(size * 8L, codecs.bits).xmap[ByteVector](_.toByteVector, _.toBitVector)
def sizeBound = codec.sizeBound
def encode(b: ByteVector) = codec.encode(b)
def decode(b: BitVector) = codec.decode(b)
override def toString = s"bytesStrict($size)"
}
}

View File

@ -6,8 +6,8 @@ import java.net.{Inet4Address, Inet6Address, InetAddress, InetSocketAddress}
import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, PublicKey, Scalar}
import fr.acinq.bitcoin.{BinaryData, Crypto}
import fr.acinq.eclair.crypto.{Generators, Sphinx}
import fr.acinq.eclair.{UInt64, wire}
import fr.acinq.eclair.wire.FixedSizeStrictCodec.bytesStrict
import fr.acinq.eclair.{UInt64, wire}
import scodec.bits.{BitVector, ByteVector}
import scodec.codecs._
import scodec.{Attempt, Codec, Err}

View File

@ -32,9 +32,9 @@ case class Ping(pongLength: Int, data: BinaryData) extends SetupMessage
case class Pong(data: BinaryData) extends SetupMessage
case class ChannelReestablish(
channelId: BinaryData,
nextLocalCommitmentNumber: Long,
nextRemoteRevocationNumber: Long) extends ChannelMessage with HasChannelId
channelId: BinaryData,
nextLocalCommitmentNumber: Long,
nextRemoteRevocationNumber: Long) extends ChannelMessage with HasChannelId
case class OpenChannel(chainHash: BinaryData,
temporaryChannelId: BinaryData,

View File

@ -27,13 +27,13 @@ import java.util.Arrays;
/**
* Implementation of the Curve25519 elliptic curve algorithm.
*
* <p>
* This implementation is based on that from arduinolibs:
* https://github.com/rweather/arduinolibs
*
* <p>
* Differences in this version are due to using 26-bit limbs for the
* representation instead of the 8/16/32-bit limbs in the original.
*
* <p>
* References: http://cr.yp.to/ecdh.html, RFC 7748
*/
public final class Curve25519 {
@ -61,25 +61,24 @@ public final class Curve25519 {
/**
* Constructs the temporary state holder for Curve25519 evaluation.
*/
private Curve25519()
{
private Curve25519() {
// Allocate memory for all of the temporary variables we will need.
x_1 = new int [NUM_LIMBS_255BIT];
x_2 = new int [NUM_LIMBS_255BIT];
x_3 = new int [NUM_LIMBS_255BIT];
z_2 = new int [NUM_LIMBS_255BIT];
z_3 = new int [NUM_LIMBS_255BIT];
A = new int [NUM_LIMBS_255BIT];
B = new int [NUM_LIMBS_255BIT];
C = new int [NUM_LIMBS_255BIT];
D = new int [NUM_LIMBS_255BIT];
E = new int [NUM_LIMBS_255BIT];
AA = new int [NUM_LIMBS_255BIT];
BB = new int [NUM_LIMBS_255BIT];
DA = new int [NUM_LIMBS_255BIT];
CB = new int [NUM_LIMBS_255BIT];
t1 = new long [NUM_LIMBS_510BIT];
t2 = new int [NUM_LIMBS_510BIT];
x_1 = new int[NUM_LIMBS_255BIT];
x_2 = new int[NUM_LIMBS_255BIT];
x_3 = new int[NUM_LIMBS_255BIT];
z_2 = new int[NUM_LIMBS_255BIT];
z_3 = new int[NUM_LIMBS_255BIT];
A = new int[NUM_LIMBS_255BIT];
B = new int[NUM_LIMBS_255BIT];
C = new int[NUM_LIMBS_255BIT];
D = new int[NUM_LIMBS_255BIT];
E = new int[NUM_LIMBS_255BIT];
AA = new int[NUM_LIMBS_255BIT];
BB = new int[NUM_LIMBS_255BIT];
DA = new int[NUM_LIMBS_255BIT];
CB = new int[NUM_LIMBS_255BIT];
t1 = new long[NUM_LIMBS_510BIT];
t2 = new int[NUM_LIMBS_510BIT];
}
@ -102,8 +101,8 @@ public final class Curve25519 {
Arrays.fill(BB, 0);
Arrays.fill(DA, 0);
Arrays.fill(CB, 0);
Arrays.fill(t1, 0L);
Arrays.fill(t2, 0);
Arrays.fill(t1, 0L);
Arrays.fill(t2, 0);
}
/**
@ -112,8 +111,7 @@ public final class Curve25519 {
*
* @param x The number to reduce, and the result.
*/
private void reduceQuick(int[] x)
{
private void reduceQuick(int[] x) {
int index, carry;
// Perform a trial subtraction of (2^255 - 19) from "x" which is
@ -142,12 +140,11 @@ public final class Curve25519 {
* Reduce a number modulo 2^255 - 19.
*
* @param result The result.
* @param x The value to be reduced. This array will be
* modified during the reduction.
* @param size The number of limbs in the high order half of x.
* @param x The value to be reduced. This array will be
* modified during the reduction.
* @param size The number of limbs in the high order half of x.
*/
private void reduce(int[] result, int[] x, int size)
{
private void reduce(int[] result, int[] x, int size) {
int index, limb, carry;
// Calculate (x mod 2^255) + ((x / 2^255) * 19) which will
@ -198,11 +195,10 @@ public final class Curve25519 {
* Multiplies two numbers modulo 2^255 - 19.
*
* @param result The result.
* @param x The first number to multiply.
* @param y The second number to multiply.
* @param x The first number to multiply.
* @param y The second number to multiply.
*/
private void mul(int[] result, int[] x, int[] y)
{
private void mul(int[] result, int[] x, int[] y) {
int i, j;
// Multiply the two numbers to create the intermediate result.
@ -220,10 +216,10 @@ public final class Curve25519 {
// Propagate carries and convert back into 26-bit words.
v = t1[0];
t2[0] = ((int)v) & 0x03FFFFFF;
t2[0] = ((int) v) & 0x03FFFFFF;
for (i = 1; i < NUM_LIMBS_510BIT; ++i) {
v = (v >> 26) + t1[i];
t2[i] = ((int)v) & 0x03FFFFFF;
t2[i] = ((int) v) & 0x03FFFFFF;
}
// Reduce the result modulo 2^255 - 19.
@ -234,10 +230,9 @@ public final class Curve25519 {
* Squares a number modulo 2^255 - 19.
*
* @param result The result.
* @param x The number to square.
* @param x The number to square.
*/
private void square(int[] result, int[] x)
{
private void square(int[] result, int[] x) {
mul(result, x, x);
}
@ -245,19 +240,18 @@ public final class Curve25519 {
* Multiplies a number by the a24 constant, modulo 2^255 - 19.
*
* @param result The result.
* @param x The number to multiply by a24.
* @param x The number to multiply by a24.
*/
private void mulA24(int[] result, int[] x)
{
private void mulA24(int[] result, int[] x) {
long a24 = 121665;
long carry = 0;
int index;
for (index = 0; index < NUM_LIMBS_255BIT; ++index) {
carry += a24 * x[index];
t2[index] = ((int)carry) & 0x03FFFFFF;
t2[index] = ((int) carry) & 0x03FFFFFF;
carry >>= 26;
}
t2[NUM_LIMBS_255BIT] = ((int)carry) & 0x03FFFFFF;
t2[NUM_LIMBS_255BIT] = ((int) carry) & 0x03FFFFFF;
reduce(result, t2, 1);
}
@ -265,11 +259,10 @@ public final class Curve25519 {
* Adds two numbers modulo 2^255 - 19.
*
* @param result The result.
* @param x The first number to add.
* @param y The second number to add.
* @param x The first number to add.
* @param y The second number to add.
*/
private void add(int[] result, int[] x, int[] y)
{
private void add(int[] result, int[] x, int[] y) {
int index, carry;
carry = x[0] + y[0];
result[0] = carry & 0x03FFFFFF;
@ -284,11 +277,10 @@ public final class Curve25519 {
* Subtracts two numbers modulo 2^255 - 19.
*
* @param result The result.
* @param x The first number to subtract.
* @param y The second number to subtract.
* @param x The first number to subtract.
* @param y The second number to subtract.
*/
private void sub(int[] result, int[] x, int[] y)
{
private void sub(int[] result, int[] x, int[] y) {
int index, borrow;
// Subtract y from x to generate the intermediate result.
@ -316,11 +308,10 @@ public final class Curve25519 {
* Conditional swap of two values.
*
* @param select Set to 1 to swap, 0 to leave as-is.
* @param x The first value.
* @param y The second value.
* @param x The first value.
* @param y The second value.
*/
private static void cswap(int select, int[] x, int[] y)
{
private static void cswap(int select, int[] x, int[] y) {
int dummy;
select = -select;
for (int index = 0; index < NUM_LIMBS_255BIT; ++index) {
@ -334,10 +325,9 @@ public final class Curve25519 {
* Raise x to the power of (2^250 - 1).
*
* @param result The result. Must not overlap with x.
* @param x The argument.
* @param x The argument.
*/
private void pow250(int[] result, int[] x)
{
private void pow250(int[] result, int[] x) {
int i, j;
// The big-endian hexadecimal expansion of (2^250 - 1) is:
@ -375,10 +365,9 @@ public final class Curve25519 {
* Computes the reciprocal of a number modulo 2^255 - 19.
*
* @param result The result. Must not overlap with x.
* @param x The argument.
* @param x The argument.
*/
private void recip(int[] result, int[] x)
{
private void recip(int[] result, int[] x) {
// The reciprocal is the same as x ^ (p - 2) where p = 2^255 - 19.
// The big-endian hexadecimal expansion of (p - 2) is:
// 7FFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFEB
@ -401,8 +390,7 @@ public final class Curve25519 {
*
* @param s The 32-byte secret key.
*/
private void evalCurve(byte[] s)
{
private void evalCurve(byte[] s) {
int sposn = 31;
int sbit = 6;
int svalue = s[sposn] | 0x40;
@ -411,7 +399,7 @@ public final class Curve25519 {
// Iterate over all 255 bits of "s" from the highest to the lowest.
// We ignore the high bit of the 256-bit representation of "s".
for (;;) {
for (; ; ) {
// Conditional swaps on entry to this bit but only if we
// didn't swap on the previous bit.
select = (svalue >> sbit) & 0x01;
@ -464,14 +452,13 @@ public final class Curve25519 {
/**
* Evaluates the Curve25519 curve.
*
* @param result Buffer to place the result of the evaluation into.
* @param offset Offset into the result buffer.
* @param result Buffer to place the result of the evaluation into.
* @param offset Offset into the result buffer.
* @param privateKey The private key to use in the evaluation.
* @param publicKey The public key to use in the evaluation, or null
* if the base point of the curve should be used.
* @param publicKey The public key to use in the evaluation, or null
* if the base point of the curve should be used.
*/
public static void eval(byte[] result, int offset, byte[] privateKey, byte[] publicKey)
{
public static void eval(byte[] result, int offset, byte[] privateKey, byte[] publicKey) {
Curve25519 state = new Curve25519();
try {
// Unpack the public key value. If null, use 9 as the base point.
@ -501,11 +488,11 @@ public final class Curve25519 {
}
// Initialize the other temporary variables.
Arrays.fill(state.x_2, 0); // x_2 = 1
Arrays.fill(state.x_2, 0); // x_2 = 1
state.x_2[0] = 1;
Arrays.fill(state.z_2, 0); // z_2 = 0
Arrays.fill(state.z_2, 0); // z_2 = 0
System.arraycopy(state.x_1, 0, state.x_3, 0, state.x_1.length); // x_3 = x_1
Arrays.fill(state.z_3, 0); // z_3 = 1
Arrays.fill(state.z_3, 0); // z_3 = 1
state.z_3[0] = 1;
// Evaluate the curve for every bit of the private key.
@ -520,9 +507,9 @@ public final class Curve25519 {
int bit = (index * 8) % 26;
int word = (index * 8) / 26;
if (bit <= (26 - 8))
result[offset + index] = (byte)(state.x_2[word] >> bit);
result[offset + index] = (byte) (state.x_2[word] >> bit);
else
result[offset + index] = (byte)((state.x_2[word] >> bit) | (state.x_2[word + 1] << (26 - bit)));
result[offset + index] = (byte) ((state.x_2[word] >> bit) | (state.x_2[word + 1] << (26 - bit)));
}
} finally {
// Clean up all temporary state before we exit.

View File

@ -2,7 +2,7 @@ package fr.acinq.eclair
import java.nio.ByteOrder
import fr.acinq.bitcoin.{BinaryData, Protocol}
import fr.acinq.bitcoin.Protocol
import fr.acinq.eclair.Features._
import org.junit.runner.RunWith
import org.scalatest.FunSuite

View File

@ -2,7 +2,6 @@ package fr.acinq.eclair
import akka.actor.{Actor, ActorLogging, ActorRef, Stash}
import fr.acinq.eclair.channel.Commitments.msg2String
import fr.acinq.eclair.channel.{INPUT_DISCONNECTED, INPUT_RECONNECTED}
import fr.acinq.eclair.wire.LightningMessage
/**

View File

@ -23,7 +23,9 @@ object TestConstants {
val seed = BinaryData("01" * 32)
val master = DeterministicWallet.generate(seed)
val extendedPrivateKey = DeterministicWallet.derivePrivateKey(master, DeterministicWallet.hardened(46) :: DeterministicWallet.hardened(0) :: Nil)
def sqlite = DriverManager.getConnection("jdbc:sqlite::memory:")
def nodeParams = NodeParams(
extendedPrivateKey = extendedPrivateKey,
privateKey = extendedPrivateKey.privateKey,
@ -58,7 +60,9 @@ object TestConstants {
channelFlags = 1,
channelExcludeDuration = 5 seconds,
watcherType = BITCOIND)
def id = nodeParams.privateKey.publicKey
def channelParams = Peer.makeChannelParams(
nodeParams = nodeParams,
defaultFinalScriptPubKey = Script.write(Script.pay2wpkh(PrivateKey(Array.fill[Byte](32)(4), compressed = true).publicKey)),
@ -72,7 +76,9 @@ object TestConstants {
val seed = BinaryData("02" * 32)
val master = DeterministicWallet.generate(seed)
val extendedPrivateKey = DeterministicWallet.derivePrivateKey(master, DeterministicWallet.hardened(46) :: DeterministicWallet.hardened(0) :: Nil)
def sqlite = DriverManager.getConnection("jdbc:sqlite::memory:")
def nodeParams = NodeParams(
extendedPrivateKey = extendedPrivateKey,
privateKey = extendedPrivateKey.privateKey,
@ -107,7 +113,9 @@ object TestConstants {
channelFlags = 1,
channelExcludeDuration = 5 seconds,
watcherType = BITCOIND)
def id = nodeParams.privateKey.publicKey
def channelParams = Peer.makeChannelParams(
nodeParams = nodeParams,
defaultFinalScriptPubKey = Script.write(Script.pay2wpkh(PrivateKey(Array.fill[Byte](32)(5), compressed = true).publicKey)),

View File

@ -5,8 +5,8 @@ import akka.testkit.{TestKit, TestProbe}
import fr.acinq.bitcoin.{BinaryData, Crypto, Transaction}
import grizzled.slf4j.Logging
import org.junit.runner.RunWith
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
import org.scalatest.junit.JUnitRunner
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
import scala.concurrent.duration._
@ -14,6 +14,7 @@ import scala.concurrent.duration._
class ElectrumClientSpec extends TestKit(ActorSystem("test")) with FunSuiteLike with Logging with BeforeAndAfterAll {
import ElectrumClient._
var client: ActorRef = _
val probe = TestProbe()
val referenceTx = Transaction.read("0200000003947e307df3ab452d23f02b5a65f4ada1804ee733e168e6197b0bd6cc79932b6c010000006a473044022069346ec6526454a481690a3664609f9e8032c34553015cfa2e9b25ebb420a33002206998f21a2aa771ad92a0c1083f4181a3acdb0d42ca51d01be1309da2ffb9cecf012102b4568cc6ee751f6d39f4a908b1fcffdb878f5f784a26a48c0acb0acff9d88e3bfeffffff966d9d969cd5f95bfd53003a35fcc1a50f4fb51f211596e6472583fdc5d38470000000006b4830450221009c9757515009c5709b5b678d678185202b817ef9a69ffb954144615ab11762210220732216384da4bf79340e9c46d0effba6ba92982cca998adfc3f354cec7715f800121035f7c3e077108035026f4ebd5d6ca696ef088d4f34d45d94eab4c41202ec74f9bfefffffff8d5062f5b04455c6cfa7e3f250e5a4fb44308ba2b86baf77f9ad0d782f57071010000006a47304402207f9f7dd91fe537a26d5554105977e3949a5c8c4ef53a6a3bff6da2d36eff928f02202b9427bef487a1825fd0c3c6851d17d5f19e6d73dfee22bf06db591929a2044d012102b4568cc6ee751f6d39f4a908b1fcffdb878f5f784a26a48c0acb0acff9d88e3bfeffffff02809698000000000017a914c82753548fdf4be1c3c7b14872c90b5198e67eaa876e642500000000001976a914e2365ec29471b3e271388b22eadf0e7f54d307a788ac6f771200")
@ -73,6 +74,6 @@ class ElectrumClientSpec extends TestKit(ActorSystem("test")) with FunSuiteLike
test("list script unspents") {
probe.send(client, ScriptHashListUnspent(scriptHash))
val ScriptHashListUnspentResponse(scriptHash1, unspents) = probe.expectMsgType[ScriptHashListUnspentResponse]
assert(unspents.contains(UnspentItem("3903726806aa044fe59f40e42eed71bded068b43aaa9e2d716e38b7825412de0", 0, 10000000L,1210224L)))
assert(unspents.contains(UnspentItem("3903726806aa044fe59f40e42eed71bded068b43aaa9e2d716e38b7825412de0", 0, 10000000L, 1210224L)))
}
}

View File

@ -1,14 +1,12 @@
package fr.acinq.eclair.blockchain.electrum
import fr.acinq.bitcoin._
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.Crypto.PrivateKey
import fr.acinq.bitcoin.DeterministicWallet.derivePrivateKey
import fr.acinq.bitcoin._
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import scala.util.Try
@RunWith(classOf[JUnitRunner])
class ElectrumWalletBasicSpec extends FunSuite {

View File

@ -9,7 +9,8 @@ import org.json4s.JsonAST._
import scala.concurrent.duration._
import scala.sys.process._
class ElectrumWalletSpec extends IntegrationSpec{
class ElectrumWalletSpec extends IntegrationSpec {
import ElectrumWallet._
val entropy = BinaryData("01" * 32)
@ -107,7 +108,7 @@ class ElectrumWalletSpec extends IntegrationSpec{
awaitCond({
val msg = listener.receiveOne(5 seconds)
msg == TransactionConfidenceChanged(BinaryData(txid),1)
msg == TransactionConfidenceChanged(BinaryData(txid), 1)
}, max = 30 seconds, interval = 1 second)
}

View File

@ -16,8 +16,8 @@ import scala.concurrent.Await
class EarnDotComFeeProviderSpec extends FunSuite {
import EarnDotComFeeProvider._
import org.json4s.jackson.JsonMethods.parse
implicit val formats = DefaultFormats
val sample_response =
@ -53,8 +53,8 @@ class EarnDotComFeeProviderSpec extends FunSuite {
}
test("make sure API hasn't changed") {
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
implicit val system = ActorSystem()
implicit val timeout = Timeout(30 seconds)
val provider = new EarnDotComFeeProvider()

View File

@ -4,8 +4,8 @@ import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import scala.concurrent.{Await, Future}
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
import scala.util.Random
@RunWith(classOf[JUnitRunner])
@ -15,6 +15,7 @@ class FallbackFeeProviderSpec extends FunSuite {
/**
* This provider returns a constant value, but fails after ttl tries
*
* @param ttl
* @param feeratesPerByte
*/
@ -25,7 +26,7 @@ class FallbackFeeProviderSpec extends FunSuite {
if (i < ttl) {
i = i + 1
Future.successful(feeratesPerByte)
} else Future.failed(new RuntimeException())
} else Future.failed(new RuntimeException())
}
def dummyFeerates = FeeratesPerByte(Random.nextInt(10000), Random.nextInt(10000), Random.nextInt(10000), Random.nextInt(10000), Random.nextInt(10000), Random.nextInt(10000))
@ -39,7 +40,7 @@ class FallbackFeeProviderSpec extends FunSuite {
val provider5 = new FailingFeeProvider(5, dummyFeerates) // fails after 5 tries
val provider7 = new FailingFeeProvider(Int.MaxValue, dummyFeerates) // "never" fails
val fallbackFeeProvider = new FallbackFeeProvider(provider0 :: provider1 :: provider3 :: provider5 :: provider7 :: Nil)
val fallbackFeeProvider = new FallbackFeeProvider(provider0 :: provider1 :: provider3 :: provider5 :: provider7 :: Nil)
assert(await(fallbackFeeProvider.getFeerates) === provider1.feeratesPerByte)
@ -58,5 +59,4 @@ class FallbackFeeProviderSpec extends FunSuite {
}
}

View File

@ -7,8 +7,7 @@ import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.blockchain.fee.FeeratesPerKw
import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.Sphinx
import fr.acinq.eclair.payment.PaymentLifecycle
import fr.acinq.eclair.payment.Hop
import fr.acinq.eclair.payment.{Hop, PaymentLifecycle}
import fr.acinq.eclair.wire._
import fr.acinq.eclair.{Globals, TestConstants}

View File

@ -1,6 +1,5 @@
package fr.acinq.eclair.channel.states.a
import akka.actor.ActorRef
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.channel.states.StateTestsHelperMethods

View File

@ -210,7 +210,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
test("recv CMD_ADD_HTLC (while waiting for a revocation)") { case (alice, _, alice2bob, _, _, _, relayer) =>
within(30 seconds) {
val sender = TestProbe()
val add1 = CMD_ADD_HTLC(TestConstants.fundingSatoshis * 2/3 * 1000, "11" * 32, 400144)
val add1 = CMD_ADD_HTLC(TestConstants.fundingSatoshis * 2 / 3 * 1000, "11" * 32, 400144)
sender.send(alice, add1)
sender.expectMsg("ok")
alice2bob.expectMsgType[UpdateAddHtlc]
@ -218,7 +218,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
sender.expectMsg("ok")
alice2bob.expectMsgType[CommitSig]
// this is over channel-capacity
val add2 = CMD_ADD_HTLC(TestConstants.fundingSatoshis * 2/3 * 1000, "22" * 32, 400144)
val add2 = CMD_ADD_HTLC(TestConstants.fundingSatoshis * 2 / 3 * 1000, "22" * 32, 400144)
sender.send(alice, add2)
//sender.expectMsgType[Failure]
relayer.expectMsgType[ForwardLocalFail]

View File

@ -8,8 +8,7 @@ import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.blockchain.fee.FeeratesPerKw
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
import fr.acinq.eclair.channel.{Data, State, _}
import fr.acinq.eclair.payment._
import fr.acinq.eclair.payment.{ForwardAdd, ForwardLocalFail, Local, PaymentLifecycle}
import fr.acinq.eclair.payment.{ForwardAdd, ForwardLocalFail, Local, PaymentLifecycle, _}
import fr.acinq.eclair.wire.{CommitSig, Error, FailureMessageCodecs, PermanentChannelFailure, RevokeAndAck, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFee, UpdateFulfillHtlc}
import fr.acinq.eclair.{Globals, TestConstants, TestkitBaseClass}
import org.junit.runner.RunWith

View File

@ -2,12 +2,12 @@ package fr.acinq.eclair.channel.states.g
import akka.actor.Status.Failure
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.eclair.{Globals, TestkitBaseClass}
import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.blockchain.fee.FeeratesPerKw
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
import fr.acinq.eclair.channel.{Data, State, _}
import fr.acinq.eclair.wire.{ClosingSigned, Error, Shutdown}
import fr.acinq.eclair.{Globals, TestkitBaseClass}
import org.junit.runner.RunWith
import org.scalatest.Tag
import org.scalatest.junit.JUnitRunner

View File

@ -7,7 +7,6 @@ import fr.acinq.eclair.TestkitBaseClass
import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
import fr.acinq.eclair.channel.{Data, State, _}
import fr.acinq.eclair.payment.HtlcGenerationSpec.paymentPreimage
import fr.acinq.eclair.payment.{AckFulfillCmd, ForwardAdd, ForwardFulfill}
import fr.acinq.eclair.transactions.Scripts
import fr.acinq.eclair.wire._

View File

@ -10,6 +10,7 @@ import org.spongycastle.util.encoders.Hex
*/
@RunWith(classOf[JUnitRunner])
class BitStreamSpec extends FunSuite {
import BitStream._
test("add bits") {

View File

@ -2,10 +2,10 @@ package fr.acinq.eclair.crypto
import fr.acinq.bitcoin.BinaryData
import fr.acinq.eclair.crypto.Noise._
import org.spongycastle.crypto.ec.CustomNamedCurves
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import org.spongycastle.crypto.ec.CustomNamedCurves
@RunWith(classOf[JUnitRunner])
class NoiseSpec extends FunSuite {

View File

@ -1,7 +1,7 @@
package fr.acinq.eclair.crypto
import fr.acinq.bitcoin.BinaryData
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.{BinaryData, Crypto}
import fr.acinq.eclair.wire._
import org.junit.runner.RunWith
import org.scalatest.FunSuite
@ -102,23 +102,23 @@ class SphinxSpec extends FunSuite {
// node #4 want to reply with an error message
val error = createErrorPacket(sharedSecret4, TemporaryNodeFailure)
assert(error == BinaryData("a5e6bd0c74cb347f10cce367f949098f2457d14c046fd8a22cb96efb30b0fdcda8cb9168b50f2fd45edd73c1b0c8b33002df376801ff58aaa94000bf8a86f92620f343baef38a580102395ae3abf9128d1047a0736ff9b83d456740ebbb4aeb3aa9737f18fb4afb4aa074fb26c4d702f42968888550a3bded8c05247e045b866baef0499f079fdaeef6538f31d44deafffdfd3afa2fb4ca9082b8f1c465371a9894dd8c243fb4847e004f5256b3e90e2edde4c9fb3082ddfe4d1e734cacd96ef0706bf63c9984e22dc98851bcccd1c3494351feb458c9c6af41c0044bea3c47552b1d992ae542b17a2d0bba1a096c78d169034ecb55b6e3a7263c26017f033031228833c1daefc0dedb8cf7c3e37c9c37ebfe42f3225c326e8bcfd338804c145b16e34e4"))
// assert(error == BinaryData("69b1e5a3e05a7b5478e6529cd1749fdd8c66da6f6db42078ff8497ac4e117e91a8cb9168b58f2fd45edd73c1b0c8b33002df376801ff58aaa94000bf8a86f92620f343baef38a580102395ae3abf9128d1047a0736ff9b83d456740ebbb4aeb3aa9737f18fb4afb4aa074fb26c4d702f42968888550a3bded8c05247e045b866baef0499f079fdaeef6538f31d44deafffdfd3afa2fb4ca9082b8f1c465371a9894dd8c2"))
// assert(error == BinaryData("69b1e5a3e05a7b5478e6529cd1749fdd8c66da6f6db42078ff8497ac4e117e91a8cb9168b58f2fd45edd73c1b0c8b33002df376801ff58aaa94000bf8a86f92620f343baef38a580102395ae3abf9128d1047a0736ff9b83d456740ebbb4aeb3aa9737f18fb4afb4aa074fb26c4d702f42968888550a3bded8c05247e045b866baef0499f079fdaeef6538f31d44deafffdfd3afa2fb4ca9082b8f1c465371a9894dd8c2"))
// error sent back to 3, 2, 1 and 0
val error1 = forwardErrorPacket(error, sharedSecret3)
assert(error1 == BinaryData("c49a1ce81680f78f5f2000cda36268de34a3f0a0662f55b4e837c83a8773c22aa081bab1616a0011585323930fa5b9fae0c85770a2279ff59ec427ad1bbff9001c0cd1497004bd2a0f68b50704cf6d6a4bf3c8b6a0833399a24b3456961ba00736785112594f65b6b2d44d9f5ea4e49b5e1ec2af978cbe31c67114440ac51a62081df0ed46d4a3df295da0b0fe25c0115019f03f15ec86fabb4c852f83449e812f141a9395b3f70b766ebbd4ec2fae2b6955bd8f32684c15abfe8fd3a6261e52650e8807a92158d9f1463261a925e4bfba44bd20b166d532f0017185c3a6ac7957adefe45559e3072c8dc35abeba835a8cb01a71a15c736911126f27d46a36168ca5ef7dccd4e2886212602b181463e0dd30185c96348f9743a02aca8ec27c0b90dca270"))
// assert(error1 == BinaryData("08cd44478211b8a4370ab1368b5ffe8c9c92fb830ff4ad6e3b0a316df9d24176a081bab161ea0011585323930fa5b9fae0c85770a2279ff59ec427ad1bbff9001c0cd1497004bd2a0f68b50704cf6d6a4bf3c8b6a0833399a24b3456961ba00736785112594f65b6b2d44d9f5ea4e49b5e1ec2af978cbe31c67114440ac51a62081df0ed46d4a3df295da0b0fe25c0115019f03f15ec86fabb4c852f83449e812f141a93"))
// assert(error1 == BinaryData("08cd44478211b8a4370ab1368b5ffe8c9c92fb830ff4ad6e3b0a316df9d24176a081bab161ea0011585323930fa5b9fae0c85770a2279ff59ec427ad1bbff9001c0cd1497004bd2a0f68b50704cf6d6a4bf3c8b6a0833399a24b3456961ba00736785112594f65b6b2d44d9f5ea4e49b5e1ec2af978cbe31c67114440ac51a62081df0ed46d4a3df295da0b0fe25c0115019f03f15ec86fabb4c852f83449e812f141a93"))
val error2 = forwardErrorPacket(error1, sharedSecret2)
assert(error2 == BinaryData("a5d3e8634cfe78b2307d87c6d90be6fe7855b4f2cc9b1dfb19e92e4b79103f61ff9ac25f412ddfb7466e74f81b3e545563cdd8f5524dae873de61d7bdfccd496af2584930d2b566b4f8d3881f8c043df92224f38cf094cfc09d92655989531524593ec6d6caec1863bdfaa79229b5020acc034cd6deeea1021c50586947b9b8e6faa83b81fbfa6133c0af5d6b07c017f7158fa94f0d206baf12dda6b68f785b773b360fd0497e16cc402d779c8d48d0fa6315536ef0660f3f4e1865f5b38ea49c7da4fd959de4e83ff3ab686f059a45c65ba2af4a6a79166aa0f496bf04d06987b6d2ea205bdb0d347718b9aeff5b61dfff344993a275b79717cd815b6ad4c0beb568c4ac9c36ff1c315ec1119a1993c4b61e6eaa0375e0aaf738ac691abd3263bf937e3"))
// assert(error2 == BinaryData("6984b0ccd86f37995857363df13670acd064bfd1a540e521cad4d71c07b1bc3dff9ac25f41addfb7466e74f81b3e545563cdd8f5524dae873de61d7bdfccd496af2584930d2b566b4f8d3881f8c043df92224f38cf094cfc09d92655989531524593ec6d6caec1863bdfaa79229b5020acc034cd6deeea1021c50586947b9b8e6faa83b81fbfa6133c0af5d6b07c017f7158fa94f0d206baf12dda6b68f785b773b360fd"))
// assert(error2 == BinaryData("6984b0ccd86f37995857363df13670acd064bfd1a540e521cad4d71c07b1bc3dff9ac25f41addfb7466e74f81b3e545563cdd8f5524dae873de61d7bdfccd496af2584930d2b566b4f8d3881f8c043df92224f38cf094cfc09d92655989531524593ec6d6caec1863bdfaa79229b5020acc034cd6deeea1021c50586947b9b8e6faa83b81fbfa6133c0af5d6b07c017f7158fa94f0d206baf12dda6b68f785b773b360fd"))
val error3 = forwardErrorPacket(error2, sharedSecret1)
assert(error3 == BinaryData("aac3200c4968f56b21f53e5e374e3a2383ad2b1b6501bbcc45abc31e59b26881b7dfadbb56ec8dae8857add94e6702fb4c3a4de22e2e669e1ed926b04447fc73034bb730f4932acd62727b75348a648a1128744657ca6a4e713b9b646c3ca66cac02cdab44dd3439890ef3aaf61708714f7375349b8da541b2548d452d84de7084bb95b3ac2345201d624d31f4d52078aa0fa05a88b4e20202bd2b86ac5b52919ea305a8949de95e935eed0319cf3cf19ebea61d76ba92532497fcdc9411d06bcd4275094d0a4a3c5d3a945e43305a5a9256e333e1f64dbca5fcd4e03a39b9012d197506e06f29339dfee3331995b21615337ae060233d39befea925cc262873e0530408e6990f1cbd233a150ef7b004ff6166c70c68d9f8c853c1abca640b8660db2921"))
// assert(error3 == BinaryData("669478a3ddf9ba4049df8fa51f73ac712b9c20380cda431696963a492713ebddb7dfadbb566c8dae8857add94e6702fb4c3a4de22e2e669e1ed926b04447fc73034bb730f4932acd62727b75348a648a1128744657ca6a4e713b9b646c3ca66cac02cdab44dd3439890ef3aaf61708714f7375349b8da541b2548d452d84de7084bb95b3ac2345201d624d31f4d52078aa0fa05a88b4e20202bd2b86ac5b52919ea305a8"))
// assert(error3 == BinaryData("669478a3ddf9ba4049df8fa51f73ac712b9c20380cda431696963a492713ebddb7dfadbb566c8dae8857add94e6702fb4c3a4de22e2e669e1ed926b04447fc73034bb730f4932acd62727b75348a648a1128744657ca6a4e713b9b646c3ca66cac02cdab44dd3439890ef3aaf61708714f7375349b8da541b2548d452d84de7084bb95b3ac2345201d624d31f4d52078aa0fa05a88b4e20202bd2b86ac5b52919ea305a8"))
val error4 = forwardErrorPacket(error3, sharedSecret0)
assert(error4 == BinaryData("9c5add3963fc7f6ed7f148623c84134b5647e1306419dbe2174e523fa9e2fbed3a06a19f899145610741c83ad40b7712aefaddec8c6baf7325d92ea4ca4d1df8bce517f7e54554608bf2bd8071a4f52a7a2f7ffbb1413edad81eeea5785aa9d990f2865dc23b4bc3c301a94eec4eabebca66be5cf638f693ec256aec514620cc28ee4a94bd9565bc4d4962b9d3641d4278fb319ed2b84de5b665f307a2db0f7fbb757366067d88c50f7e829138fde4f78d39b5b5802f1b92a8a820865af5cc79f9f30bc3f461c66af95d13e5e1f0381c184572a91dee1c849048a647a1158cf884064deddbf1b0b88dfe2f791428d0ba0f6fb2f04e14081f69165ae66d9297c118f0907705c9c4954a199bae0bb96fad763d690e7daa6cfda59ba7f2c8d11448b604d12d"))
// assert(error4 == BinaryData("500d8596f76d3045bfdbf99914b98519fe76ea130dc22338c473ab68d74378b13a06a19f891145610741c83ad40b7712aefaddec8c6baf7325d92ea4ca4d1df8bce517f7e54554608bf2bd8071a4f52a7a2f7ffbb1413edad81eeea5785aa9d990f2865dc23b4bc3c301a94eec4eabebca66be5cf638f693ec256aec514620cc28ee4a94bd9565bc4d4962b9d3641d4278fb319ed2b84de5b665f307a2db0f7fbb757366"))
// assert(error4 == BinaryData("500d8596f76d3045bfdbf99914b98519fe76ea130dc22338c473ab68d74378b13a06a19f891145610741c83ad40b7712aefaddec8c6baf7325d92ea4ca4d1df8bce517f7e54554608bf2bd8071a4f52a7a2f7ffbb1413edad81eeea5785aa9d990f2865dc23b4bc3c301a94eec4eabebca66be5cf638f693ec256aec514620cc28ee4a94bd9565bc4d4962b9d3641d4278fb319ed2b84de5b665f307a2db0f7fbb757366"))
// origin parses error packet and can see that it comes from node #4

View File

@ -6,10 +6,10 @@ import fr.acinq.eclair.channel.Helpers.Funding
import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.{ShaChain, Sphinx}
import fr.acinq.eclair.payment.{Local, Relayed}
import fr.acinq.eclair.{UInt64, randomKey}
import fr.acinq.eclair.transactions.Transactions.CommitTx
import fr.acinq.eclair.transactions._
import fr.acinq.eclair.wire.{ChannelCodecs, CommitSig, UpdateAddHtlc}
import fr.acinq.eclair.wire.{ChannelCodecs, UpdateAddHtlc}
import fr.acinq.eclair.{UInt64, randomKey}
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner

View File

@ -7,7 +7,6 @@ import fr.acinq.bitcoin.{Block, Crypto}
import fr.acinq.eclair.db.sqlite.SqliteNetworkDb
import fr.acinq.eclair.randomKey
import fr.acinq.eclair.router.Announcements
import fr.acinq.eclair.wire.LightningMessageCodecs.channelAnnouncementCodec
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner

View File

@ -364,6 +364,7 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with FunSuiteLike wit
/**
* We currently use p2pkh script Helpers.getFinalScriptPubKey
*
* @param scriptPubKey
* @return
*/

View File

@ -1,11 +1,10 @@
package fr.acinq.eclair.interop.rustytests
import java.io.{BufferedWriter, File, FileWriter}
import java.util.UUID
import java.util.concurrent.CountDownLatch
import akka.actor.{Actor, ActorLogging, ActorRef, Stash}
import fr.acinq.bitcoin.{BinaryData, Crypto}
import fr.acinq.bitcoin.BinaryData
import fr.acinq.eclair.channel._
import fr.acinq.eclair.transactions.{IN, OUT}

View File

@ -3,9 +3,9 @@ package fr.acinq.eclair.payment
import fr.acinq.bitcoin.{BinaryData, Block, Crypto}
import fr.acinq.eclair.crypto.Sphinx
import fr.acinq.eclair.crypto.Sphinx.{PacketAndSecrets, ParsedPacket}
import fr.acinq.eclair.payment.PaymentHop.nodeFee
import fr.acinq.eclair.payment.PaymentLifecycle._
import fr.acinq.eclair.randomKey
import fr.acinq.eclair.payment.PaymentHop.nodeFee
import fr.acinq.eclair.wire.{ChannelUpdate, LightningMessageCodecs, PerHopPayload}
import org.junit.runner.RunWith
import org.scalatest.FunSuite

View File

@ -1,7 +1,6 @@
package fr.acinq.eclair.payment
import akka.actor.FSM.{CurrentState, SubscribeTransitionCallBack, Transition}
import akka.actor.Status.Failure
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.bitcoin.MilliSatoshi
import fr.acinq.eclair.Globals
@ -12,7 +11,6 @@ import fr.acinq.eclair.router._
import fr.acinq.eclair.wire._
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.matchers.FailureMessage
/**
* Created by PM on 29/08/2016.

View File

@ -1,7 +1,7 @@
package fr.acinq.eclair.router
import fr.acinq.bitcoin.{BinaryData, Block}
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.{BinaryData, Block}
import fr.acinq.eclair.TestConstants.Alice
import fr.acinq.eclair._
import fr.acinq.eclair.router.Announcements._

View File

@ -92,9 +92,9 @@ abstract class BaseRouterSpec extends TestkitBaseClass {
// and answers with valid scripts
watcher.send(router, ParallelGetResponse(
IndividualResult(chan_ab, Some(Transaction(version = 0, txIn = Nil, txOut = TxOut(Satoshi(1000000), write(pay2wsh(Scripts.multiSig2of2(funding_a, funding_b)))) :: Nil, lockTime = 0)), true) ::
IndividualResult(chan_bc, Some(Transaction(version = 0, txIn = Nil, txOut = TxOut(Satoshi(1000000), write(pay2wsh(Scripts.multiSig2of2(funding_b, funding_c)))) :: Nil, lockTime = 0)), true) ::
IndividualResult(chan_cd, Some(Transaction(version = 0, txIn = Nil, txOut = TxOut(Satoshi(1000000), write(pay2wsh(Scripts.multiSig2of2(funding_c, funding_d)))) :: Nil, lockTime = 0)), true) ::
IndividualResult(chan_ef, Some(Transaction(version = 0, txIn = Nil, txOut = TxOut(Satoshi(1000000), write(pay2wsh(Scripts.multiSig2of2(funding_e, funding_f)))) :: Nil, lockTime = 0)), true) :: Nil
IndividualResult(chan_bc, Some(Transaction(version = 0, txIn = Nil, txOut = TxOut(Satoshi(1000000), write(pay2wsh(Scripts.multiSig2of2(funding_b, funding_c)))) :: Nil, lockTime = 0)), true) ::
IndividualResult(chan_cd, Some(Transaction(version = 0, txIn = Nil, txOut = TxOut(Satoshi(1000000), write(pay2wsh(Scripts.multiSig2of2(funding_c, funding_d)))) :: Nil, lockTime = 0)), true) ::
IndividualResult(chan_ef, Some(Transaction(version = 0, txIn = Nil, txOut = TxOut(Satoshi(1000000), write(pay2wsh(Scripts.multiSig2of2(funding_e, funding_f)))) :: Nil, lockTime = 0)), true) :: Nil
))
// watcher receives watch-spent request
watcher.expectMsgType[WatchSpentBasic]

View File

@ -3,9 +3,9 @@ package fr.acinq.eclair.router
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.{BinaryData, Block, Crypto, MilliSatoshi}
import fr.acinq.eclair.payment.PaymentRequest.ExtraHop
import fr.acinq.eclair.{Globals, randomKey, toShortId}
import fr.acinq.eclair.wire.{ChannelAnnouncement, ChannelUpdate, PerHopPayload}
import fr.acinq.eclair.payment._
import fr.acinq.eclair.wire.{ChannelAnnouncement, ChannelUpdate, PerHopPayload}
import fr.acinq.eclair.{Globals, randomKey, toShortId}
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
@ -49,7 +49,7 @@ class RouteCalculationSpec extends FunSuite {
ChannelDesc(5L, d, e)
)
val routes = for(i <- 0 until 10) yield Router.findRouteDijkstra(a, e, channels)
val routes = for (i <- 0 until 10) yield Router.findRouteDijkstra(a, e, channels)
assert(routes.exists(_ != routes.head))
}
@ -201,7 +201,7 @@ class RouteCalculationSpec extends FunSuite {
val extraRoute = PaymentHop.buildExtra(reverseRoute, amount.amount)
assert(extraRoute === List(ExtraHop(PublicKey("02f0b230e53723ccc331db140edc518be1ee5ab29a508104a4be2f5be922c928e8"), 24412456671576064L, 547005, 144),
ExtraHop(PublicKey("032b4af42b5e8089a7a06005ead9ac4667527390ee39c998b7b0307f0d81d7f4ac") ,23366821113626624L, 547000, 144)))
ExtraHop(PublicKey("032b4af42b5e8089a7a06005ead9ac4667527390ee39c998b7b0307f0d81d7f4ac"), 23366821113626624L, 547000, 144)))
// Sender side
@ -223,11 +223,16 @@ class RouteCalculationSpec extends FunSuite {
test("stale channels pruning") {
// set current block height
Globals.blockCount.set(500000)
// we only care about timestamps
def channelAnnouncement(shortChannelId: Long) = ChannelAnnouncement("", "", "", "", "", "", shortChannelId, randomKey.publicKey, randomKey.publicKey, randomKey.publicKey, randomKey.publicKey)
def channelUpdate(shortChannelId: Long, timestamp: Long) = ChannelUpdate("", "", shortChannelId, timestamp, "", 0, 0, 0, 0)
def desc(shortChannelId: Long) = ChannelDesc(shortChannelId, randomKey.publicKey, randomKey.publicKey)
def daysAgoInBlocks(daysAgo: Int): Int = Globals.blockCount.get().toInt - 144 * daysAgo
def daysAgoInSeconds(daysAgo: Int): Long = Platform.currentTime / 1000 - daysAgo * 24 * 3600
// a is an old channel with an old channel update => PRUNED

View File

@ -13,7 +13,6 @@ import fr.acinq.eclair.{randomKey, toShortId}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import scala.concurrent.Future
import scala.concurrent.duration._
/**

View File

@ -1,14 +1,12 @@
package fr.acinq.eclair.transactions
import fr.acinq.bitcoin._
import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, PublicKey, Scalar}
import fr.acinq.bitcoin._
import fr.acinq.eclair.channel.Helpers.Funding
import fr.acinq.eclair.crypto.Generators
import fr.acinq.eclair.transactions.Transactions.{HtlcSuccessTx, HtlcTimeoutTx, TransactionWithInputInfo}
import fr.acinq.eclair.wire.UpdateAddHtlc
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import scala.io.Source

View File

@ -5,8 +5,8 @@ import java.net.{InetAddress, InetSocketAddress}
import fr.acinq.bitcoin.Crypto.{PrivateKey, Scalar}
import fr.acinq.bitcoin.{BinaryData, Block, Crypto}
import fr.acinq.eclair.crypto.Sphinx
import fr.acinq.eclair.wire.LightningMessageCodecs._
import fr.acinq.eclair.{UInt64, randomBytes, randomKey}
import fr.acinq.eclair.wire.LightningMessageCodecs.{lightningMessageCodec, rgb, socketaddress, uint64ex, zeropaddedstring}
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner

View File

@ -9,9 +9,9 @@
<GridPane styleClass="grid" prefWidth="400.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="140.0" prefWidth="150.0" maxWidth="180.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="30.0" prefWidth="30.0" />
<ColumnConstraints hgrow="ALWAYS" minWidth="30.0" prefWidth="30.0"/>
<ColumnConstraints hgrow="SOMETIMES" minWidth="40.0" prefWidth="60.0" maxWidth="60.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="30.0" prefWidth="40.0" />
<ColumnConstraints hgrow="ALWAYS" minWidth="30.0" prefWidth="40.0"/>
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
@ -24,8 +24,9 @@
<HBox GridPane.columnSpan="4" GridPane.columnIndex="0" alignment="CENTER" spacing="10.0">
<children>
<TextField fx:id="channelId" text="N/A" editable="false" styleClass="noteditable, text-strong"
HBox.hgrow="ALWAYS" focusTraversable="false" />
<HBox GridPane.columnIndex="4" GridPane.halignment="RIGHT" alignment="CENTER_RIGHT" HBox.hgrow="NEVER" spacing="5.0">
HBox.hgrow="ALWAYS" focusTraversable="false"/>
<HBox GridPane.columnIndex="4" GridPane.halignment="RIGHT" alignment="CENTER_RIGHT"
HBox.hgrow="NEVER" spacing="5.0">
<children>
<Button fx:id="close" mnemonicParsing="false" styleClass="close-channel" text="Close"/>
</children>
@ -42,11 +43,13 @@
GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.rowIndex="2"/>
<Label styleClass="text-muted" text="Your balance (milliBTC)" GridPane.rowIndex="3"/>
<TextField fx:id="amountUs" text="N/A" focusTraversable="false" editable="false" styleClass="noteditable"
<TextField fx:id="amountUs" text="N/A" focusTraversable="false" editable="false"
styleClass="noteditable"
GridPane.columnIndex="1" GridPane.rowIndex="3"/>
<Label styleClass="text-muted" text="Capacity (milliBTC)" GridPane.rowIndex="4"/>
<TextField fx:id="capacity" text="N/A" focusTraversable="false" editable="false" styleClass="noteditable"
<TextField fx:id="capacity" text="N/A" focusTraversable="false" editable="false"
styleClass="noteditable"
GridPane.columnIndex="1" GridPane.rowIndex="4"/>
<Label styleClass="text-muted" text="Funder" GridPane.columnIndex="2" GridPane.rowIndex="3"/>

View File

@ -4,13 +4,14 @@
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.*?>
<?import java.net.URL?>
<?import javafx.scene.shape.Rectangle?>
<?import javafx.scene.text.TextFlow?>
<?import javafx.scene.text.Text?>
<AnchorPane fx:id="root" minHeight="300.0" prefHeight="400.0" styleClass="root" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<?import javafx.scene.text.*?>
<?import java.net.URL?>
<AnchorPane fx:id="root" minHeight="300.0" prefHeight="400.0" styleClass="root" xmlns="http://javafx.com/javafx/8"
xmlns:fx="http://javafx.com/fxml/1">
<children>
<BorderPane AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0" AnchorPane.topAnchor="0" AnchorPane.bottomAnchor="0">
<BorderPane AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0" AnchorPane.topAnchor="0"
AnchorPane.bottomAnchor="0">
<center>
<TabPane tabClosingPolicy="UNAVAILABLE" BorderPane.alignment="CENTER">
<tabs>
@ -40,12 +41,17 @@
<VBox spacing="10.0" styleClass="grid">
<children>
<TableView fx:id="networkNodesTable" minHeight="50.0" prefHeight="5000.0">
<columnResizePolicy><TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/></columnResizePolicy>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
</columnResizePolicy>
<columns>
<TableColumn fx:id="networkNodesRGBColumn" minWidth="20.0" prefWidth="20.0" maxWidth="20.0" text="" sortable="false"/>
<TableColumn fx:id="networkNodesAliasColumn" minWidth="80.0" prefWidth="180.0" maxWidth="300.0" text="Alias"/>
<TableColumn fx:id="networkNodesRGBColumn" minWidth="20.0"
prefWidth="20.0" maxWidth="20.0" text="" sortable="false"/>
<TableColumn fx:id="networkNodesAliasColumn" minWidth="80.0"
prefWidth="180.0" maxWidth="300.0" text="Alias"/>
<TableColumn fx:id="networkNodesIdColumn" text="Node Id"/>
<TableColumn fx:id="networkNodesIPColumn" minWidth="150.0" prefWidth="250.0" maxWidth="300.0" text="IP"/>
<TableColumn fx:id="networkNodesIPColumn" minWidth="150.0"
prefWidth="250.0" maxWidth="300.0" text="IP"/>
</columns>
</TableView>
</children>
@ -57,11 +63,16 @@
<VBox spacing="10.0" styleClass="grid">
<children>
<TableView fx:id="networkChannelsTable" minHeight="50.0" prefHeight="5000.0">
<columnResizePolicy><TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/></columnResizePolicy>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
</columnResizePolicy>
<columns>
<TableColumn fx:id="networkChannelsIdColumn" minWidth="120.0" prefWidth="170.0" maxWidth="300.0" text="Short Channel Id"/>
<TableColumn fx:id="networkChannelsIdColumn" minWidth="120.0"
prefWidth="170.0" maxWidth="300.0"
text="Short Channel Id"/>
<TableColumn fx:id="networkChannelsNode1Column" text="Node 1"/>
<TableColumn fx:id="networkChannelsDirectionsColumn" minWidth="30.0" prefWidth="30.0" maxWidth="30.0"/>
<TableColumn fx:id="networkChannelsDirectionsColumn" minWidth="30.0"
prefWidth="30.0" maxWidth="30.0"/>
<TableColumn fx:id="networkChannelsNode2Column" text="Node 2"/>
</columns>
</TableView>
@ -73,51 +84,89 @@
<content>
<AnchorPane>
<children>
<TabPane AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" AnchorPane.bottomAnchor="0.0"
styleClass="activities-tab" tabClosingPolicy="UNAVAILABLE" BorderPane.alignment="CENTER">
<TabPane AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0"
AnchorPane.topAnchor="0.0" AnchorPane.bottomAnchor="0.0"
styleClass="activities-tab" tabClosingPolicy="UNAVAILABLE"
BorderPane.alignment="CENTER">
<tabs>
<Tab fx:id="paymentSentTab" closable="false" text="Sent">
<TableView fx:id="paymentSentTable" minHeight="50.0" prefHeight="5000.0">
<columnResizePolicy><TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/></columnResizePolicy>
<TableView fx:id="paymentSentTable" minHeight="50.0"
prefHeight="5000.0">
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
</columnResizePolicy>
<columns>
<TableColumn fx:id="paymentSentDateColumn" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0" text="Date"/>
<TableColumn fx:id="paymentSentAmountColumn" text="Amount (msat)"
styleClass="align-right" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0"/>
<TableColumn fx:id="paymentSentFeesColumn" text="Fees Paid (msat)"
styleClass="align-right" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0"/>
<TableColumn fx:id="paymentSentHashColumn" text="Payment Hash"/>
<TableColumn fx:id="paymentSentDateColumn" resizable="false"
minWidth="150.0" prefWidth="150.0"
maxWidth="150.0" text="Date"/>
<TableColumn fx:id="paymentSentAmountColumn"
text="Amount (msat)"
styleClass="align-right" resizable="false"
minWidth="150.0" prefWidth="150.0"
maxWidth="150.0"/>
<TableColumn fx:id="paymentSentFeesColumn"
text="Fees Paid (msat)"
styleClass="align-right" resizable="false"
minWidth="150.0" prefWidth="150.0"
maxWidth="150.0"/>
<TableColumn fx:id="paymentSentHashColumn"
text="Payment Hash"/>
</columns>
</TableView>
</Tab>
<Tab fx:id="paymentReceivedTab" closable="false" text="Received">
<TableView fx:id="paymentReceivedTable" minHeight="50.0" prefHeight="5000.0">
<columnResizePolicy><TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/></columnResizePolicy>
<TableView fx:id="paymentReceivedTable" minHeight="50.0"
prefHeight="5000.0">
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
</columnResizePolicy>
<columns>
<TableColumn fx:id="paymentReceivedDateColumn" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0" text="Date"/>
<TableColumn fx:id="paymentReceivedAmountColumn" text="Amount (msat)"
styleClass="align-right" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0"/>
<TableColumn fx:id="paymentReceivedHashColumn" text="Payment Hash"/>
<TableColumn fx:id="paymentReceivedDateColumn"
resizable="false" minWidth="150.0"
prefWidth="150.0" maxWidth="150.0"
text="Date"/>
<TableColumn fx:id="paymentReceivedAmountColumn"
text="Amount (msat)"
styleClass="align-right" resizable="false"
minWidth="150.0" prefWidth="150.0"
maxWidth="150.0"/>
<TableColumn fx:id="paymentReceivedHashColumn"
text="Payment Hash"/>
</columns>
</TableView>
</Tab>
<Tab fx:id="paymentRelayedTab" closable="false" text="Relayed">
<TableView fx:id="paymentRelayedTable" minHeight="50.0" prefHeight="5000.0">
<columnResizePolicy><TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/></columnResizePolicy>
<TableView fx:id="paymentRelayedTable" minHeight="50.0"
prefHeight="5000.0">
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
</columnResizePolicy>
<columns>
<TableColumn fx:id="paymentRelayedDateColumn" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0" text="Date"/>
<TableColumn fx:id="paymentRelayedAmountColumn" text="Amount (msat)"
styleClass="align-right" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0"/>
<TableColumn fx:id="paymentRelayedFeesColumn" text="Fees Earned (msat)"
styleClass="align-right" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0"/>
<TableColumn fx:id="paymentRelayedHashColumn" text="Payment Hash"/>
<TableColumn fx:id="paymentRelayedDateColumn"
resizable="false" minWidth="150.0"
prefWidth="150.0" maxWidth="150.0"
text="Date"/>
<TableColumn fx:id="paymentRelayedAmountColumn"
text="Amount (msat)"
styleClass="align-right" resizable="false"
minWidth="150.0" prefWidth="150.0"
maxWidth="150.0"/>
<TableColumn fx:id="paymentRelayedFeesColumn"
text="Fees Earned (msat)"
styleClass="align-right" resizable="false"
minWidth="150.0" prefWidth="150.0"
maxWidth="150.0"/>
<TableColumn fx:id="paymentRelayedHashColumn"
text="Payment Hash"/>
</columns>
</TableView>
</Tab>
</tabs>
</TabPane>
<Label AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" textAlignment="RIGHT"
<Label AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"
textAlignment="RIGHT"
maxWidth="180.0" wrapText="true" styleClass="activity-disclaimer"
text="Payment history will be cleared when the node is shut down." />
text="Payment history will be cleared when the node is shut down."/>
</children>
</AnchorPane>
</content>
@ -130,8 +179,11 @@
<children>
<HBox alignment="CENTER_LEFT" HBox.hgrow="ALWAYS" onContextMenuRequested="#openNodeIdContext">
<children>
<ImageView fitHeight="16.0" fitWidth="27.0" opacity="0.52" pickOnBounds="true" preserveRatio="true">
<image><Image url="@../commons/images/eclair-shape.png"/></image>
<ImageView fitHeight="16.0" fitWidth="27.0" opacity="0.52" pickOnBounds="true"
preserveRatio="true">
<image>
<Image url="@../commons/images/eclair-shape.png"/>
</image>
</ImageView>
<Label fx:id="labelNodeId" text="N/A"/>
</children>
@ -139,7 +191,7 @@
<HBox alignment="CENTER_LEFT" HBox.hgrow="SOMETIMES" minWidth="80.0">
<children>
<Separator orientation="VERTICAL"/>
<Rectangle fx:id="rectRGB" width="7" height="7" fill="transparent" />
<Rectangle fx:id="rectRGB" width="7" height="7" fill="transparent"/>
<Label fx:id="labelAlias" text="N/A"/>
</children>
</HBox>
@ -190,7 +242,8 @@
</Menu>
<Menu mnemonicParsing="false" text="Tools">
<items>
<MenuItem mnemonicParsing="false" onAction="#handleExportDot" text="Export Graph to .dot"/>
<MenuItem mnemonicParsing="false" onAction="#handleExportDot"
text="Export Graph to .dot"/>
</items>
</Menu>
<Menu mnemonicParsing="false" text="Help">
@ -203,28 +256,35 @@
</top>
</BorderPane>
<StackPane fx:id="blocker" styleClass="blocker-cover" opacity="0" visible="false" alignment="CENTER"
AnchorPane.topAnchor="0" AnchorPane.leftAnchor="0" AnchorPane.bottomAnchor="0" AnchorPane.rightAnchor="0">
AnchorPane.topAnchor="0" AnchorPane.leftAnchor="0" AnchorPane.bottomAnchor="0"
AnchorPane.rightAnchor="0">
<children>
<HBox fx:id="blockerDialog" opacity="0" styleClass="blocker-dialog" fillHeight="false" alignment="CENTER_LEFT" spacing="20"
minWidth="430.0" minHeight="100.0" prefWidth="430.0" prefHeight="100.0" maxWidth="430.0" maxHeight="100.0">
<HBox fx:id="blockerDialog" opacity="0" styleClass="blocker-dialog" fillHeight="false"
alignment="CENTER_LEFT" spacing="20"
minWidth="430.0" minHeight="100.0" prefWidth="430.0" prefHeight="100.0" maxWidth="430.0"
maxHeight="100.0">
<children>
<ImageView fitHeight="40.0" fitWidth="40.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../commons/images/connection_icon.png" />
<Image url="@../commons/images/connection_icon.png"/>
</image>
</ImageView>
<VBox spacing="10.0" GridPane.columnIndex="1">
<children>
<TextFlow>
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" styleClass="text-strong" text="Lost connection to " />
<Text fx:id="blockerDialogTitleEngineName" strokeType="OUTSIDE" strokeWidth="0.0" styleClass="text-strong" />
<Text strokeType="OUTSIDE" styleClass="text-strong" strokeWidth="0.0" text="..." />
<Text strokeType="OUTSIDE" strokeWidth="0.0" styleClass="text-strong"
text="Lost connection to "/>
<Text fx:id="blockerDialogTitleEngineName" strokeType="OUTSIDE"
strokeWidth="0.0" styleClass="text-strong"/>
<Text strokeType="OUTSIDE" styleClass="text-strong" strokeWidth="0.0"
text="..."/>
</children>
</TextFlow>
<TextFlow>
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" styleClass="text-sm" text="Please check your connection." />
<Text strokeType="OUTSIDE" strokeWidth="0.0" styleClass="text-sm"
text="Please check your connection."/>
</children>
</TextFlow>
</children>

View File

@ -1,15 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.String?>
<?import java.net.URL?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.*?>
<?import java.lang.String?>
<?import java.net.URL?>
<GridPane fx:id="rootPane" minWidth="300.0" prefWidth="300.0" maxWidth="300.0"
xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
opacity="0" onMouseEntered="#handleMouseEnter" onMouseExited="#handleMouseExit">
@ -29,9 +26,11 @@
<Image url="@../commons/images/eclair-square.png"/>
</image>
</ImageView>
<Button fx:id="closeButton" maxHeight="18.0" maxWidth="18.0" minHeight="18.0" minWidth="18.0" mnemonicParsing="false"
styleClass="notification-close" text="" GridPane.columnIndex="2" />
<Label fx:id="messageLabel" styleClass="notification-message" text="N/A" wrapText="false" GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="1"/>
<Button fx:id="closeButton" maxHeight="18.0" maxWidth="18.0" minHeight="18.0" minWidth="18.0"
mnemonicParsing="false"
styleClass="notification-close" text="" GridPane.columnIndex="2"/>
<Label fx:id="messageLabel" styleClass="notification-message" text="N/A" wrapText="false"
GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="1"/>
</children>
<styleClass>
<String fx:value="grid"/>

View File

@ -1,13 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.net.URL?>
<?import javafx.scene.layout.VBox?>
<?import java.net.URL?>
<VBox fx:id="notifsVBox" spacing="10.0"
style="-fx-background-color: transparent" styleClass="notifications-box"
xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<stylesheets>
<URL value="@../commons/globals.css" />
<URL value="@main.css" />
</stylesheets>
<stylesheets>
<URL value="@../commons/globals.css"/>
<URL value="@main.css"/>
</stylesheets>
</VBox>

View File

@ -1,64 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.String?>
<?import java.net.URL?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Text?>
<?import javafx.scene.text.TextFlow?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?import java.lang.String?>
<?import java.net.URL?>
<GridPane prefWidth="500.0" prefHeight="200.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<columnConstraints>
<ColumnConstraints halignment="LEFT" hgrow="SOMETIMES" minWidth="10.0" maxWidth="120.0" />
<ColumnConstraints halignment="LEFT" hgrow="SOMETIMES" minWidth="10.0" prefWidth="180.0" />
<ColumnConstraints halignment="LEFT" hgrow="SOMETIMES" minWidth="10.0" maxWidth="120.0"/>
<ColumnConstraints halignment="LEFT" hgrow="SOMETIMES" minWidth="10.0" prefWidth="180.0"/>
</columnConstraints>
<children>
<ImageView fitHeight="120.0" fitWidth="120.0" pickOnBounds="true" preserveRatio="true" GridPane.halignment="CENTER">
<ImageView fitHeight="120.0" fitWidth="120.0" pickOnBounds="true" preserveRatio="true"
GridPane.halignment="CENTER">
<image>
<Image url="@../commons/images/eclair-square.png" />
<Image url="@../commons/images/eclair-square.png"/>
</image>
</ImageView>
<VBox spacing="10.0" styleClass="about-content" GridPane.columnIndex="1">
<children>
<TextFlow>
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" styleClass="text-strong" text="Eclair v" />
<Text fx:id="version" strokeType="OUTSIDE" strokeWidth="0.0" styleClass="text-strong" text="Unknown" />
<Text strokeType="OUTSIDE" styleClass="text-sm" strokeWidth="0.0" text=" brought to you by " />
<Text onMouseClicked="#openACINQPage" strokeType="OUTSIDE" strokeWidth="0.0" styleClass="link" text="ACINQ" />
<Text strokeType="OUTSIDE" strokeWidth="0.0" styleClass="text-strong" text="Eclair v"/>
<Text fx:id="version" strokeType="OUTSIDE" strokeWidth="0.0" styleClass="text-strong"
text="Unknown"/>
<Text strokeType="OUTSIDE" styleClass="text-sm" strokeWidth="0.0" text=" brought to you by "/>
<Text onMouseClicked="#openACINQPage" strokeType="OUTSIDE" strokeWidth="0.0" styleClass="link"
text="ACINQ"/>
</children>
</TextFlow>
<TextFlow layoutX="10.0" layoutY="90.0">
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Eclair follows " />
<Text onMouseClicked="#openLNRFCPage" strokeType="OUTSIDE" strokeWidth="0.0" styleClass="link" text="the Lightning Network specifications" />
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="." />
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Eclair follows "/>
<Text onMouseClicked="#openLNRFCPage" strokeType="OUTSIDE" strokeWidth="0.0" styleClass="link"
text="the Lightning Network specifications"/>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="."/>
</children>
</TextFlow>
<TextFlow layoutX="10.0" layoutY="10.0">
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="The source code is available from " />
<Text onMouseClicked="#openGithubPage" strokeType="OUTSIDE" strokeWidth="0.0" styleClass="link" text="GitHub" />
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="." />
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="The source code is available from "/>
<Text onMouseClicked="#openGithubPage" strokeType="OUTSIDE" strokeWidth="0.0" styleClass="link"
text="GitHub"/>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="."/>
</children>
</TextFlow>
<TextFlow layoutX="10.0" layoutY="90.0" styleClass="">
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Licensed under " />
<Text onMouseClicked="#openApacheLicencePage" strokeType="OUTSIDE" strokeWidth="0.0" styleClass="link" text="the Apache 2 License" />
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="." />
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Licensed under "/>
<Text onMouseClicked="#openApacheLicencePage" strokeType="OUTSIDE" strokeWidth="0.0"
styleClass="link" text="the Apache 2 License"/>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="."/>
</children>
</TextFlow>
</children>
</VBox>
</children>
<styleClass>
<String fx:value="grid" />
<String fx:value="grid"/>
</styleClass>
<stylesheets>
<URL value="@../commons/globals.css" />
<URL value="@../commons/globals.css"/>
</stylesheets>
</GridPane>

View File

@ -1,89 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.collections.FXCollections?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import java.lang.String?>
<?import java.net.URL?>
<?import javafx.collections.FXCollections?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.CheckBox?>
<GridPane styleClass="grid" prefWidth="550.0" prefHeight="350.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<GridPane styleClass="grid" prefWidth="550.0" prefHeight="350.0" xmlns="http://javafx.com/javafx/8"
xmlns:fx="http://javafx.com/fxml/1">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="180.0" minWidth="10.0" prefWidth="180.0" halignment="RIGHT" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="180.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="160.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="180.0" minWidth="10.0" prefWidth="180.0" halignment="RIGHT"/>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="180.0"/>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="160.0"/>
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES" maxHeight="10.0"/>
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES" minHeight="30.0" valignment="BOTTOM"/>
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints vgrow="SOMETIMES"/>
<RowConstraints vgrow="SOMETIMES"/>
</rowConstraints>
<children>
<VBox alignment="CENTER_RIGHT" GridPane.rowIndex="0">
<children>
<Label styleClass="text-strong" text="Target Node URI" />
<Label styleClass="label-description" text="Address of the node" textAlignment="RIGHT" wrapText="true" />
<Label styleClass="text-strong" text="Target Node URI"/>
<Label styleClass="label-description" text="Address of the node" textAlignment="RIGHT" wrapText="true"/>
</children>
</VBox>
<TextField fx:id="host" prefWidth="313.0" promptText="pubkey@host:port"
GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="0" />
<Label fx:id="hostError" opacity="0.0" styleClass="text-error, text-error-downward" text="Generic Invalid URI" mouseTransparent="true"
GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.columnSpan="2" />
GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="0"/>
<Label fx:id="hostError" opacity="0.0" styleClass="text-error, text-error-downward" text="Generic Invalid URI"
mouseTransparent="true"
GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.columnSpan="2"/>
<CheckBox fx:id="simpleConnection" mnemonicParsing="false" text="Simple connection (no channel)" styleClass="text-sm"
GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="1" />
<CheckBox fx:id="simpleConnection" mnemonicParsing="false" text="Simple connection (no channel)"
styleClass="text-sm"
GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="1"/>
<VBox alignment="CENTER_RIGHT" GridPane.rowIndex="2">
<children>
<Label styleClass="text-strong" text="Capacity" />
<Label styleClass="label-description" text="Funding capacity of the channel" textAlignment="RIGHT" wrapText="true" />
<Label styleClass="text-strong" text="Capacity"/>
<Label styleClass="label-description" text="Funding capacity of the channel" textAlignment="RIGHT"
wrapText="true"/>
</children>
</VBox>
<TextField fx:id="fundingSatoshis" prefWidth="313.0" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<TextField fx:id="fundingSatoshis" prefWidth="313.0" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
<ComboBox fx:id="unit" prefWidth="150.0" GridPane.columnIndex="2" GridPane.rowIndex="2">
<items>
<FXCollections fx:factory="observableArrayList">
<String fx:id="milliBTC" fx:value="milliBTC" />
<String fx:id="Satoshi" fx:value="Satoshi" />
<String fx:id="milliSatoshi" fx:value="milliSatoshi" />
<String fx:id="milliBTC" fx:value="milliBTC"/>
<String fx:id="Satoshi" fx:value="Satoshi"/>
<String fx:id="milliSatoshi" fx:value="milliSatoshi"/>
</FXCollections>
</items>
</ComboBox>
<Label fx:id="fundingSatoshisError" opacity="0.0" styleClass="text-error, text-error-downward" text="Generic Invalid Funding"
GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="2" />
<Label fx:id="fundingSatoshisError" opacity="0.0" styleClass="text-error, text-error-downward"
text="Generic Invalid Funding"
GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="2"/>
<Label styleClass="text-muted" text="Optional Parameters" wrapText="true" GridPane.columnIndex="0" GridPane.rowIndex="3" />
<Separator styleClass="options-separator" GridPane.columnIndex="1" GridPane.rowIndex="3" GridPane.columnSpan="2"/>
<Label styleClass="text-muted" text="Optional Parameters" wrapText="true" GridPane.columnIndex="0"
GridPane.rowIndex="3"/>
<Separator styleClass="options-separator" GridPane.columnIndex="1" GridPane.rowIndex="3"
GridPane.columnSpan="2"/>
<VBox alignment="CENTER_RIGHT" GridPane.rowIndex="4">
<children>
<Label styleClass="text-strong" text="Push Amount (msat)" />
<Label styleClass="label-description" text="Sent when opening channel" textAlignment="RIGHT" wrapText="true" />
<Label styleClass="text-strong" text="Push Amount (msat)"/>
<Label styleClass="label-description" text="Sent when opening channel" textAlignment="RIGHT"
wrapText="true"/>
</children>
</VBox>
<TextField fx:id="pushMsat" prefWidth="313.0" GridPane.columnIndex="1" GridPane.rowIndex="4" />
<Label fx:id="pushMsatError" opacity="0.0" styleClass="text-error, text-error-downward" text="Generic Invalid Push"
GridPane.columnIndex="1" GridPane.rowIndex="4" />
<CheckBox fx:id="publicChannel" mnemonicParsing="true" selected="true" styleClass="text-sm" text="Public Channel"
GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="5" />
<TextField fx:id="pushMsat" prefWidth="313.0" GridPane.columnIndex="1" GridPane.rowIndex="4"/>
<Label fx:id="pushMsatError" opacity="0.0" styleClass="text-error, text-error-downward"
text="Generic Invalid Push"
GridPane.columnIndex="1" GridPane.rowIndex="4"/>
<CheckBox fx:id="publicChannel" mnemonicParsing="true" selected="true" styleClass="text-sm"
text="Public Channel"
GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="5"/>
<Button fx:id="button" defaultButton="true" mnemonicParsing="false" onAction="#handleOpen" text="Connect"
GridPane.columnIndex="1" GridPane.rowIndex="6" GridPane.valignment="BOTTOM" />
GridPane.columnIndex="1" GridPane.rowIndex="6" GridPane.valignment="BOTTOM"/>
<Button cancelButton="true" mnemonicParsing="false" onAction="#handleClose" styleClass="cancel" text="Cancel"
GridPane.columnIndex="2" GridPane.halignment="RIGHT" GridPane.rowIndex="6" GridPane.valignment="BOTTOM" />
GridPane.columnIndex="2" GridPane.halignment="RIGHT" GridPane.rowIndex="6"
GridPane.valignment="BOTTOM"/>
</children>
<stylesheets>
<URL value="@../commons/globals.css" />
<URL value="@../commons/globals.css"/>
</stylesheets>
</GridPane>

View File

@ -10,7 +10,8 @@
<children>
<GridPane styleClass="grid">
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES" maxWidth="250.0" minWidth="10.0" prefWidth="250.0"/>
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES" maxWidth="250.0" minWidth="10.0"
prefWidth="250.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="120.0"/>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="120.0"/>
</columnConstraints>
@ -22,35 +23,41 @@
<children>
<VBox alignment="TOP_RIGHT" GridPane.rowIndex="0">
<children>
<Label styleClass="text-strong" text="Amount to receive" />
<Label styleClass="label-description" wrapText="true" textAlignment="RIGHT" text="Maximum of ~0.042 BTC" />
<Label styleClass="text-strong" text="Amount to receive"/>
<Label styleClass="label-description" wrapText="true" textAlignment="RIGHT"
text="Maximum of ~0.042 BTC"/>
</children>
</VBox>
<TextField fx:id="amount" GridPane.columnIndex="1" GridPane.rowIndex="0"/>
<ComboBox fx:id="unit" GridPane.columnIndex="2" GridPane.rowIndex="0" GridPane.halignment="RIGHT">
<items>
<FXCollections fx:factory="observableArrayList">
<String fx:id="milliBTC" fx:value="milliBTC" />
<String fx:id="Satoshi" fx:value="Satoshi" />
<String fx:id="milliSatoshi" fx:value="milliSatoshi" />
<String fx:id="milliBTC" fx:value="milliBTC"/>
<String fx:id="Satoshi" fx:value="Satoshi"/>
<String fx:id="milliSatoshi" fx:value="milliSatoshi"/>
</FXCollections>
</items>
</ComboBox>
<Label fx:id="amountError" opacity="0.0" styleClass="text-error, text-error-downward" text="Generic Invalid Amount"
<Label fx:id="amountError" opacity="0.0" styleClass="text-error, text-error-downward"
text="Generic Invalid Amount"
mouseTransparent="true" GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.columnSpan="2"/>
<VBox alignment="TOP_RIGHT" GridPane.rowIndex="1" GridPane.columnIndex="0">
<children>
<Label styleClass="text-strong" text="Optional description" />
<Label styleClass="label-description" wrapText="true" textAlignment="RIGHT" text="Can be left empty" />
<Label styleClass="text-strong" text="Optional description"/>
<Label styleClass="label-description" wrapText="true" textAlignment="RIGHT"
text="Can be left empty"/>
</children>
</VBox>
<TextArea fx:id="description" GridPane.columnIndex="1" GridPane.rowIndex="1" GridPane.columnSpan="2" wrapText="true" prefHeight="50.0" />
<TextArea fx:id="description" GridPane.columnIndex="1" GridPane.rowIndex="1" GridPane.columnSpan="2"
wrapText="true" prefHeight="50.0"/>
<Button defaultButton="true" mnemonicParsing="false" onAction="#handleGenerate" prefHeight="29.0"
prefWidth="95.0" text="Generate" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
<Button cancelButton="true" mnemonicParsing="false" onAction="#handleClose" styleClass="cancel" text="Close"
GridPane.columnIndex="2" GridPane.halignment="RIGHT" GridPane.rowIndex="2" opacity="0" focusTraversable="false"/>
<Button cancelButton="true" mnemonicParsing="false" onAction="#handleClose" styleClass="cancel"
text="Close"
GridPane.columnIndex="2" GridPane.halignment="RIGHT" GridPane.rowIndex="2" opacity="0"
focusTraversable="false"/>
</children>
</GridPane>
@ -69,12 +76,14 @@
<children>
<HBox spacing="10.0" alignment="CENTER_LEFT">
<children>
<Label text="Invoice:" styleClass="text-strong" />
<Button mnemonicParsing="false" onAction="#handleCopyInvoice" styleClass="copy-clipboard"
text="Copy to Clipboard" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<Label text="Invoice:" styleClass="text-strong"/>
<Button mnemonicParsing="false" onAction="#handleCopyInvoice"
styleClass="copy-clipboard"
text="Copy to Clipboard" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
</children>
</HBox>
<TextArea fx:id="paymentRequestTextArea" prefHeight="200.0" editable="false" styleClass="noteditable, text-sm, text-mono" wrapText="true" />
<TextArea fx:id="paymentRequestTextArea" prefHeight="200.0" editable="false"
styleClass="noteditable, text-sm, text-mono" wrapText="true"/>
</children>
</VBox>
</children>

View File

@ -4,7 +4,8 @@
<?import javafx.scene.layout.*?>
<?import java.lang.String?>
<?import java.net.URL?>
<GridPane fx:id="nodeId" prefWidth="450.0" prefHeight="450.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<GridPane fx:id="nodeId" prefWidth="450.0" prefHeight="450.0" xmlns="http://javafx.com/javafx/8"
xmlns:fx="http://javafx.com/fxml/1">
<columnConstraints>
<ColumnConstraints halignment="LEFT" hgrow="SOMETIMES" minWidth="10.0" prefWidth="110.0"/>
<ColumnConstraints halignment="LEFT" hgrow="ALWAYS" minWidth="10.0" prefWidth="250.0"/>
@ -28,13 +29,16 @@
styleClass="text-error" GridPane.columnSpan="2" GridPane.rowIndex="2"/>
<Label styleClass="text-muted" text="Amount (msat)" GridPane.halignment="RIGHT" GridPane.rowIndex="3"/>
<TextField fx:id="amountField" focusTraversable="false" editable="false" styleClass="noteditable" text="0" GridPane.columnIndex="1" GridPane.rowIndex="3"/>
<TextField fx:id="amountField" focusTraversable="false" editable="false" styleClass="noteditable" text="0"
GridPane.columnIndex="1" GridPane.rowIndex="3"/>
<Label styleClass="text-muted" text="Node Id" GridPane.halignment="RIGHT" GridPane.rowIndex="4"/>
<TextField fx:id="nodeIdField" focusTraversable="false" editable="false" styleClass="noteditable" text="N/A" GridPane.columnIndex="1" GridPane.rowIndex="4"/>
<TextField fx:id="nodeIdField" focusTraversable="false" editable="false" styleClass="noteditable" text="N/A"
GridPane.columnIndex="1" GridPane.rowIndex="4"/>
<Label styleClass="text-muted" text="hash" GridPane.halignment="RIGHT" GridPane.rowIndex="5"/>
<TextField fx:id="hashField" focusTraversable="false" editable="false" styleClass="noteditable" text="N/A" GridPane.columnIndex="1" GridPane.rowIndex="5"/>
<TextField fx:id="hashField" focusTraversable="false" editable="false" styleClass="noteditable" text="N/A"
GridPane.columnIndex="1" GridPane.rowIndex="5"/>
<Separator GridPane.columnSpan="2" GridPane.rowIndex="6"/>

View File

@ -3,47 +3,53 @@
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.effect.BoxBlur?>
<?import javafx.scene.image.*?>
<?import javafx.scene.effect.DropShadow?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.*?>
<?import java.net.URL?>
<?import javafx.scene.effect.DropShadow?>
<Pane fx:id="splash" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
prefHeight="457.0" prefWidth="760.0" style="-fx-background-color: transparent"
xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<ImageView fx:id="imgBlurred" fitHeight="0" fitWidth="300.0" layoutX="176.0" layoutY="115.0" pickOnBounds="true" preserveRatio="true">
<ImageView fx:id="imgBlurred" fitHeight="0" fitWidth="300.0" layoutX="176.0" layoutY="115.0" pickOnBounds="true"
preserveRatio="true">
<image>
<Image url="@../commons/images/eclair-fit.png" />
<Image url="@../commons/images/eclair-fit.png"/>
</image>
<effect>
<BoxBlur height="114.75" width="92.44" />
<BoxBlur height="114.75" width="92.44"/>
</effect>
</ImageView>
<ImageView fx:id="img" fitHeight="0" fitWidth="409.0" layoutX="176.0" layoutY="114.0" opacity="0.0" pickOnBounds="true" preserveRatio="true">
<ImageView fx:id="img" fitHeight="0" fitWidth="409.0" layoutX="176.0" layoutY="114.0" opacity="0.0"
pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../commons/images/eclair-fit.png" />
<Image url="@../commons/images/eclair-fit.png"/>
</image>
</ImageView>
<VBox fx:id="errorBox" opacity="0.0" alignment="CENTER" layoutX="196.0" prefWidth="370.0" prefHeight="457.0">
<children>
<VBox prefWidth="370.0" styleClass="error-box" spacing="10">
<effect>
<DropShadow offsetX="5.0" offsetY="5.0" radius="25.0" color="rgba(0,0,0,.4)" blurType="GAUSSIAN"/>
<DropShadow offsetX="5.0" offsetY="5.0" radius="25.0" color="rgba(0,0,0,.4)"
blurType="GAUSSIAN"/>
</effect>
<children>
<VBox fx:id="logBox" VBox.vgrow="ALWAYS" styleClass="log-box">
<children>
</children>
</VBox>
<Label onMouseClicked="#openGithubPage" VBox.vgrow="NEVER" styleClass="link" text="Consult our readme to get started." />
<Button fx:id="closeButton" VBox.vgrow="NEVER" mnemonicParsing="false" onAction="#closeAndKill" text="Close" cancelButton="true" />
<Label onMouseClicked="#openGithubPage" VBox.vgrow="NEVER" styleClass="link"
text="Consult our readme to get started."/>
<Button fx:id="closeButton" VBox.vgrow="NEVER" mnemonicParsing="false" onAction="#closeAndKill"
text="Close" cancelButton="true"/>
</children>
</VBox>
</children>
</VBox>
</children>
<stylesheets>
<URL value="@../commons/globals.css" />
<URL value="@splash.css" />
<URL value="@../commons/globals.css"/>
<URL value="@splash.css"/>
</stylesheets>
</Pane>

View File

@ -12,7 +12,9 @@ import fr.acinq.eclair.gui.controllers.SplashController
import grizzled.slf4j.Logging
sealed trait AppNotificationType
case object SuccessAppNotification extends AppNotificationType
case object InfoAppNotification extends AppNotificationType
case class AppNotification(notificationType: AppNotificationType, message: String) extends PreloaderNotification

View File

@ -12,7 +12,6 @@ import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin._
import fr.acinq.eclair.blockchain.bitcoind.zmq.ZMQActor.{ZMQConnected, ZMQDisconnected}
import fr.acinq.eclair.blockchain.electrum.ElectrumClient.{ElectrumConnected, ElectrumDisconnected}
import fr.acinq.eclair.blockchain.electrum.ElectrumWallet
import fr.acinq.eclair.channel._
import fr.acinq.eclair.gui.controllers._
import fr.acinq.eclair.gui.utils.CoinFormat

View File

@ -7,8 +7,8 @@ import java.util.Locale
import akka.pattern.ask
import akka.util.Timeout
import fr.acinq.bitcoin.{BinaryData, MilliSatoshi}
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin.{BinaryData, MilliSatoshi}
import fr.acinq.eclair._
import fr.acinq.eclair.gui.controllers._
import fr.acinq.eclair.gui.utils.GUIValidators
@ -16,8 +16,8 @@ import fr.acinq.eclair.io.Switchboard.{NewChannel, NewConnection}
import fr.acinq.eclair.payment._
import grizzled.slf4j.Logging
import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success}
/**
@ -29,7 +29,7 @@ class Handlers(fKit: Future[Kit])(implicit ec: ExecutionContext = ExecutionConte
private var notifsController: Option[NotificationsController] = None
def initNotifications (controller: NotificationsController) = {
def initNotifications(controller: NotificationsController) = {
notifsController = Option(controller)
}
@ -67,21 +67,23 @@ class Handlers(fKit: Future[Kit])(implicit ec: ExecutionContext = ExecutionConte
kit <- fKit
res <- (kit.paymentInitiator ? request).mapTo[PaymentResult]
} yield res)
.onComplete {
case Success(_: PaymentSucceeded) =>
val message = s"${NumberFormat.getInstance(Locale.getDefault).format(amountMsat/1000)} satoshis"
notification("Payment Sent", message, NOTIFICATION_SUCCESS)
case Success(PaymentFailed(_, failures)) =>
val message = s"${failures.lastOption match {
case Some(LocalFailure(t)) => t.getMessage
case Some(RemoteFailure(_, e)) => e.failureMessage
case _ => "Unknown error"
}} (${failures.size} attempts)"
notification("Payment Failed", message, NOTIFICATION_ERROR)
case Failure(t) =>
val message = t.getMessage
notification("Payment Failed", message, NOTIFICATION_ERROR)
}
.onComplete {
case Success(_: PaymentSucceeded) =>
val message = s"${NumberFormat.getInstance(Locale.getDefault).format(amountMsat / 1000)} satoshis"
notification("Payment Sent", message, NOTIFICATION_SUCCESS)
case Success(PaymentFailed(_, failures)) =>
val message = s"${
failures.lastOption match {
case Some(LocalFailure(t)) => t.getMessage
case Some(RemoteFailure(_, e)) => e.failureMessage
case _ => "Unknown error"
}
} (${failures.size} attempts)"
notification("Payment Failed", message, NOTIFICATION_ERROR)
case Failure(t) =>
val message = t.getMessage
notification("Payment Failed", message, NOTIFICATION_ERROR)
}
}
def receive(amountMsat: MilliSatoshi, description: String): Future[String] = for {
@ -108,12 +110,12 @@ class Handlers(fKit: Future[Kit])(implicit ec: ExecutionContext = ExecutionConte
/**
* Displays a system notification if the system supports it.
*
* @param title Title of the notification
* @param message main message of the notification, will not wrap
* @param title Title of the notification
* @param message main message of the notification, will not wrap
* @param notificationType type of the message, default to NONE
* @param showAppName true if you want the notification title to be preceded by "Eclair - ". True by default
* @param showAppName true if you want the notification title to be preceded by "Eclair - ". True by default
*/
def notification (title: String, message: String, notificationType: NotificationType = NOTIFICATION_NONE, showAppName: Boolean = true) = {
def notification(title: String, message: String, notificationType: NotificationType = NOTIFICATION_NONE, showAppName: Boolean = true) = {
notifsController.map(_.addNotification(if (showAppName) s"Eclair - $title" else title, message, notificationType))
}
}

View File

@ -1,8 +1,9 @@
package fr.acinq.eclair.gui.controllers
import javafx.fxml.FXML
import javafx.application.HostServices
import javafx.fxml.FXML
import javafx.scene.text.Text
import grizzled.slf4j.Logging
/**
@ -11,11 +12,16 @@ import grizzled.slf4j.Logging
class AboutController(hostServices: HostServices) extends Logging {
@FXML var version: Text = _
@FXML def initialize = {
version.setText(getClass.getPackage.getImplementationVersion)
}
@FXML def openApacheLicencePage = hostServices.showDocument("https://www.apache.org/licenses/LICENSE-2.0")
@FXML def openACINQPage = hostServices.showDocument("https://acinq.co")
@FXML def openGithubPage = hostServices.showDocument("https://github.com/ACINQ/eclair")
@FXML def openLNRFCPage = hostServices.showDocument("https://github.com/lightningnetwork/lightning-rfc")
}

View File

@ -61,7 +61,7 @@ class ChannelPaneController(val theirNodeIdValue: String) extends Logging {
if (contextMenu != null) contextMenu.hide
}
def updateRemoteNodeAlias (alias: String) {
def updateRemoteNodeAlias(alias: String) {
Option(nodeId).map((n: TextField) => n.setText(s"$theirNodeIdValue ($alias)"))
}
}

View File

@ -217,6 +217,7 @@ class MainController(val handlers: Handlers, val hostServices: HostServices) ext
val directionImage = new ImageView
directionImage.setFitWidth(20)
directionImage.setFitHeight(20)
override def updateItem(item: ChannelInfo, empty: Boolean): Unit = {
super.updateItem(item, empty)
if (item == null || empty) {
@ -224,19 +225,19 @@ class MainController(val handlers: Handlers, val hostServices: HostServices) ext
setText(null)
} else {
item match {
case ChannelInfo(_ , Some(true), Some(true)) =>
case ChannelInfo(_, Some(true), Some(true)) =>
directionImage.setImage(new Image("/gui/commons/images/in-out-11.png", false))
setTooltip(new Tooltip("Both Node 1 and Node 2 are enabled"))
setGraphic(directionImage)
case ChannelInfo(_ , Some(true), Some(false)) =>
case ChannelInfo(_, Some(true), Some(false)) =>
directionImage.setImage(new Image("/gui/commons/images/in-out-10.png", false))
setTooltip(new Tooltip("Node 1 is enabled, but not Node 2"))
setGraphic(directionImage)
case ChannelInfo(_ , Some(false), Some(true)) =>
case ChannelInfo(_, Some(false), Some(true)) =>
directionImage.setImage(new Image("/gui/commons/images/in-out-01.png", false))
setTooltip(new Tooltip("Node 2 is enabled, but not Node 1"))
setGraphic(directionImage)
case ChannelInfo(_ , Some(false), Some(false)) =>
case ChannelInfo(_, Some(false), Some(false)) =>
directionImage.setImage(new Image("/gui/commons/images/in-out-00.png", false))
setTooltip(new Tooltip("Neither Node 1 nor Node 2 is enabled"))
setGraphic(directionImage)

View File

@ -20,6 +20,7 @@ class NotificationPaneController {
@FXML def handleMouseEnter(event: MouseEvent) = {
rootPane.setOpacity(1)
}
@FXML def handleMouseExit(event: MouseEvent) = {
rootPane.setOpacity(0.95)
}

View File

@ -12,9 +12,13 @@ import javafx.util.Duration
import grizzled.slf4j.Logging
sealed trait NotificationType
case object NOTIFICATION_NONE extends NotificationType
case object NOTIFICATION_SUCCESS extends NotificationType
case object NOTIFICATION_ERROR extends NotificationType
case object NOTIFICATION_INFO extends NotificationType
/**
@ -30,11 +34,11 @@ class NotificationsController extends Logging {
/**
* Adds a notification panel to the notifications stage
*
* @param title Title of the notification, should not be too long
* @param message Main message of the notification
* @param title Title of the notification, should not be too long
* @param message Main message of the notification
* @param notificationType type of the notification (error, warning, success, info...)
*/
def addNotification (title: String, message: String, notificationType: NotificationType) = {
def addNotification(title: String, message: String, notificationType: NotificationType) = {
val loader = new FXMLLoader(getClass.getResource("/gui/main/notificationPane.fxml"))
val notifPaneController = new NotificationPaneController

View File

@ -8,7 +8,6 @@ import javafx.scene.control._
import javafx.stage.Stage
import fr.acinq.bitcoin.{MilliSatoshi, Satoshi}
import fr.acinq.eclair.Setup
import fr.acinq.eclair.channel.ChannelFlags
import fr.acinq.eclair.gui.Handlers
import fr.acinq.eclair.gui.utils.GUIValidators
@ -71,7 +70,7 @@ class OpenChannelController(val handlers: Handlers, val stage: Stage) extends Lo
// pushMsat is optional, so we validate field only if it isn't empty
if (GUIValidators.validate(pushMsat.getText, pushMsatError, "Push msat must be numeric", GUIValidators.amountRegex)
&& GUIValidators.validate(pushMsatError, "Push msat must be 16 777 216 000 msat (~0.167 BTC) or less", pushMsat.getText.toLong <= maxPushMsat)) {
val channelFlags = if(publicChannel.isSelected) ChannelFlags.AnnounceChannel else ChannelFlags.Empty
val channelFlags = if (publicChannel.isSelected) ChannelFlags.AnnounceChannel else ChannelFlags.Empty
handlers.open(host.getText, Some(NewChannel(smartFunding, MilliSatoshi(pushMsat.getText.toLong), Some(channelFlags))))
stage.close
}

View File

@ -13,7 +13,6 @@ import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
import com.google.zxing.{BarcodeFormat, EncodeHintType}
import fr.acinq.bitcoin.MilliSatoshi
import fr.acinq.eclair.Setup
import fr.acinq.eclair.gui.Handlers
import fr.acinq.eclair.gui.utils.{ContextMenuUtils, GUIValidators}
import fr.acinq.eclair.payment.PaymentRequest

View File

@ -8,9 +8,6 @@ import javafx.scene.input.KeyCode.{ENTER, TAB}
import javafx.scene.input.KeyEvent
import javafx.stage.Stage
import fr.acinq.bitcoin.BinaryData
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.eclair.Setup
import fr.acinq.eclair.gui.Handlers
import fr.acinq.eclair.gui.utils.GUIValidators
import fr.acinq.eclair.payment.PaymentRequest

Some files were not shown because too many files have changed in this diff Show More