1
0
mirror of https://github.com/ACINQ/eclair.git synced 2024-11-20 10:39:19 +01:00

Added pushMsat param when connecting to a channel

* pushMsat is an optional field in millisatoshi, default is 0
* In channel creation, `amount` parameter has been renamed to fundingSatoshis
  to make code more readable
* Added pushMsat field in open channel modal GUI
* Added description label in open channel modal to improve user experience
This commit is contained in:
dpad85 2017-01-30 14:47:59 +01:00
parent 2d2ecdabf2
commit de99fdb566
10 changed files with 79 additions and 40 deletions

View File

@ -76,4 +76,9 @@
}
.text-error.text-error-upward {
-fx-translate-y: -6px;
}
.label-description {
-fx-text-fill: rgb(156,159,161);
-fx-font-size: 10px;
}

View File

@ -10,6 +10,7 @@
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<GridPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefWidth="528.0"
styleClass="grid" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
@ -23,11 +24,13 @@
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints minHeight="1.0" prefHeight="3.0" vgrow="SOMETIMES" />
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints minHeight="1.0" prefHeight="3.0" vgrow="SOMETIMES" />
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="50.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<TextField fx:id="host" prefWidth="313.0" promptText="pubkey@host:port1" GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="1" />
<TextField fx:id="amount" prefWidth="313.0" GridPane.columnIndex="1" GridPane.rowIndex="3" />
<TextField fx:id="fundingSatoshis" prefWidth="313.0" GridPane.columnIndex="1" GridPane.rowIndex="3" />
<ComboBox fx:id="unit" prefWidth="150.0" GridPane.columnIndex="2" GridPane.rowIndex="3">
<items>
<FXCollections fx:factory="observableArrayList">
@ -37,12 +40,30 @@
</FXCollections>
</items>
</ComboBox>
<Button fx:id="button" defaultButton="true" mnemonicParsing="false" onAction="#handleOpen" text="Connect" GridPane.columnIndex="1" GridPane.rowIndex="4" GridPane.valignment="BOTTOM" />
<Label text="Target Node URI" GridPane.rowIndex="1" />
<Label text="Amount" GridPane.rowIndex="3" />
<TextField fx:id="pushMsat" layoutX="193.0" layoutY="112.0" prefWidth="313.0" GridPane.columnIndex="1" GridPane.rowIndex="5" />
<Button fx:id="button" defaultButton="true" mnemonicParsing="false" onAction="#handleOpen" text="Connect" 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" />
<VBox alignment="CENTER_RIGHT" GridPane.rowIndex="3">
<children>
<Label styleClass="text-strong" text="Capacity *" />
<Label styleClass="label-description" text="Funding capacity of the channel." textAlignment="RIGHT" wrapText="true" />
</children>
</VBox>
<Label fx:id="hostError" opacity="0.0" styleClass="text-error" text="Please use a valid url (pubkey@host:port)" GridPane.columnIndex="1" GridPane.columnSpan="2" />
<Label fx:id="amountError" opacity="0.0" styleClass="text-error" text="Please use a valid amount" GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="2" />
<Button cancelButton="true" mnemonicParsing="false" onAction="#handleClose" styleClass="cancel" text="Cancel" GridPane.columnIndex="2" GridPane.halignment="RIGHT" GridPane.rowIndex="4" GridPane.valignment="BOTTOM" />
<Label fx:id="fundingSatoshisError" opacity="0.0" styleClass="text-error" text="Please use a valid amount" GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="2" />
<Label fx:id="pushMsatError" layoutX="193.0" layoutY="89.0" opacity="0.0" styleClass="text-error" text="Please use a valid Push Amount" GridPane.columnIndex="1" GridPane.rowIndex="4" />
<VBox alignment="CENTER_RIGHT" layoutX="24.0" layoutY="112.0" GridPane.rowIndex="5">
<children>
<Label styleClass="text-strong" text="Push Amount" />
<Label styleClass="label-description" text="Optional amount in millisatoshi. Sent when opening channel" textAlignment="RIGHT" wrapText="true" />
</children>
</VBox>
<VBox alignment="CENTER_RIGHT" layoutX="24.0" layoutY="112.0" GridPane.rowIndex="1">
<children>
<Label styleClass="text-strong" text="Target Node URI *" />
<Label styleClass="label-description" text="Address of the node" textAlignment="RIGHT" wrapText="true" />
</children>
</VBox>
</children>
<stylesheets>
<URL value="@modals.css" />

View File

@ -8,7 +8,7 @@ import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import akka.util.Timeout
import com.typesafe.config.ConfigFactory
import fr.acinq.bitcoin.{Base58Check, BinaryData, OP_CHECKSIG, OP_DUP, OP_EQUALVERIFY, OP_HASH160, OP_PUSHDATA, Satoshi}
import fr.acinq.bitcoin.{Base58Check, BinaryData, MilliSatoshi, OP_CHECKSIG, OP_DUP, OP_EQUALVERIFY, OP_HASH160, OP_PUSHDATA, Satoshi}
import fr.acinq.eclair.api.Service
import fr.acinq.eclair.blockchain.peer.PeerClient
import fr.acinq.eclair.blockchain.rpc.BitcoinJsonRPCClient
@ -98,7 +98,7 @@ class Setup() extends Logging {
override val paymentHandler: ActorRef = _setup.paymentHandler
override val paymentInitiator: ActorRef = _setup.paymentInitiator
override def connect(host: String, port: Int, pubkey: BinaryData, amount: Satoshi): Unit = system.actorOf(Client.props(host, port, pubkey, amount, register))
override def connect(host: String, port: Int, pubkey: BinaryData, fundingSatoshis: Satoshi, pushMsat: MilliSatoshi): Unit = system.actorOf(Client.props(host, port, pubkey, fundingSatoshis, pushMsat, register))
}
Http().bindAndHandle(api.route, config.getString("eclair.api.host"), config.getInt("eclair.api.port")) onFailure {
case t: Throwable => system.eventStream.publish(HTTPBindError)

View File

@ -11,7 +11,7 @@ import akka.pattern.ask
import akka.util.Timeout
import de.heikoseeberger.akkahttpjson4s.Json4sSupport
import de.heikoseeberger.akkahttpjson4s.Json4sSupport.ShouldWritePretty
import fr.acinq.bitcoin.{BinaryData, Satoshi}
import fr.acinq.bitcoin.{BinaryData, MilliSatoshi, Satoshi}
import fr.acinq.eclair._
import fr.acinq.eclair.channel.Register.{ListChannels, SendCommand}
import fr.acinq.eclair.channel._
@ -47,7 +47,7 @@ trait Service extends Logging {
import Json4sSupport.{json4sMarshaller, json4sUnmarshaller}
def connect(host: String, port: Int, pubkey: BinaryData, amount: Satoshi): Unit
def connect(host: String, port: Int, pubkey: BinaryData, fundingSatoshis: Satoshi, pushMsat: MilliSatoshi): Unit
def register: ActorRef
@ -71,7 +71,7 @@ trait Service extends Logging {
req =>
val f_res: Future[AnyRef] = req match {
case JsonRPCBody(_, _, "connect", JString(host) :: JInt(port) :: JString(pubkey) :: JInt(anchor_amount) :: Nil) =>
connect(host, port.toInt, BinaryData(pubkey), Satoshi(anchor_amount.toLong))
connect(host, port.toInt, BinaryData(pubkey), Satoshi(anchor_amount.toLong), MilliSatoshi(0))
Future.successful("ok")
case JsonRPCBody(_, _, "info", _) =>
Future.successful(Status(Globals.Node.id))

View File

@ -3,7 +3,7 @@ package fr.acinq.eclair.channel
import akka.actor.{Props, _}
import akka.util.Timeout
import fr.acinq.bitcoin.Crypto.PrivateKey
import fr.acinq.bitcoin.{BinaryData, DeterministicWallet, Satoshi, ScriptElt}
import fr.acinq.bitcoin.{BinaryData, DeterministicWallet, MilliSatoshi, Satoshi, ScriptElt}
import fr.acinq.eclair.Globals
import fr.acinq.eclair.crypto.Noise.KeyPair
import fr.acinq.eclair.crypto.TransportHandler
@ -39,7 +39,7 @@ class Register(watcher: ActorRef, router: ActorRef, paymentHandler: ActorRef, de
def receive: Receive = main(0L)
def main(counter: Long): Receive = {
case CreateChannel(connection, pubkey, amount_opt) =>
case CreateChannel(connection, pubkey, funding_opt, pushmsat_opt) =>
def generateKey(index: Long): PrivateKey = DeterministicWallet.derivePrivateKey(Globals.Node.extendedPrivateKey, index :: counter :: Nil).privateKey
val localParams = LocalParams(
@ -56,14 +56,17 @@ class Register(watcher: ActorRef, router: ActorRef, paymentHandler: ActorRef, de
delayedPaymentKey = generateKey(3),
defaultFinalScriptPubKey = defaultFinalScriptPubKey,
shaSeed = Globals.Node.seed,
isFunder = amount_opt.isDefined
isFunder = funding_opt.isDefined
)
def makeChannel(conn: ActorRef, publicKey: BinaryData, ctx: ActorContext): ActorRef = {
// note that we use transport's context and not register's context
val channel = ctx.actorOf(Channel.props(conn, watcher, router, paymentHandler, localParams, publicKey.toString(), Some(Globals.autosign_interval)), s"channel-$counter")
amount_opt match {
case Some(amount) => channel ! INPUT_INIT_FUNDER(amount.amount, 0)
funding_opt match {
case Some(funding) => pushmsat_opt match {
case Some(pushmsat) => channel ! INPUT_INIT_FUNDER(funding.amount, pushmsat.amount)
case None => channel ! INPUT_INIT_FUNDER(funding.amount, 0)
}
case None => channel ! INPUT_INIT_FUNDEE()
}
channel
@ -73,7 +76,7 @@ class Register(watcher: ActorRef, router: ActorRef, paymentHandler: ActorRef, de
new TransportHandler[LightningMessage](
KeyPair(Globals.Node.publicKey.toBin, Globals.Node.privateKey.toBin),
pubkey,
isWriter = amount_opt.isDefined,
isWriter = funding_opt.isDefined,
them = connection,
listenerFactory = makeChannel,
serializer = LightningMessageSerializer)),
@ -99,7 +102,7 @@ object Register {
def props(blockchain: ActorRef, router: ActorRef, paymentHandler: ActorRef, defaultFinalScriptPubKey: Seq[ScriptElt]) = Props(classOf[Register], blockchain, router, paymentHandler, defaultFinalScriptPubKey)
// @formatter:off
case class CreateChannel(connection: ActorRef, pubkey: Option[BinaryData], anchorAmount: Option[Satoshi])
case class CreateChannel(connection: ActorRef, pubkey: Option[BinaryData], fundingSatoshis: Option[Satoshi], pushMsat: Option[MilliSatoshi])
case class ListChannels()

View File

@ -4,7 +4,7 @@ package fr.acinq.eclair.gui
import javafx.application.Platform
import javafx.scene.control.{TextArea, TextField}
import fr.acinq.bitcoin.{BinaryData, Satoshi}
import fr.acinq.bitcoin.{BinaryData, MilliSatoshi, Satoshi}
import fr.acinq.eclair._
import fr.acinq.eclair.io.Client
import fr.acinq.eclair.payment.CreatePayment
@ -17,12 +17,12 @@ class Handlers(setup: Setup) extends Logging {
import setup._
def open(hostPort: String, amount: Satoshi) = {
def open(hostPort: String, fundingSatoshis: Satoshi, pushMsat: MilliSatoshi) = {
val regex = "([a-fA-F0-9]+)@([a-zA-Z0-9\\.\\-_]+):([0-9]+)".r
hostPort match {
case regex(pubkey, host, port) =>
logger.info(s"connecting to $host:$port")
system.actorOf(Client.props(host, port.toInt, pubkey, amount, register))
system.actorOf(Client.props(host, port.toInt, pubkey, fundingSatoshis, pushMsat, register))
case _ => {}
}
}

View File

@ -5,7 +5,7 @@ import javafx.fxml.FXML
import javafx.scene.control.{Button, ComboBox, Label, TextField}
import javafx.stage.Stage
import fr.acinq.bitcoin.Satoshi
import fr.acinq.bitcoin.{MilliSatoshi, Satoshi}
import fr.acinq.eclair.Setup
import fr.acinq.eclair.gui.Handlers
import fr.acinq.eclair.gui.utils.GUIValidators
@ -18,8 +18,10 @@ class OpenChannelController(val handlers: Handlers, val stage: Stage, val setup:
@FXML var host: TextField = _
@FXML var hostError: Label = _
@FXML var amount: TextField = _
@FXML var amountError: Label = _
@FXML var fundingSatoshis: TextField = _
@FXML var fundingSatoshisError: Label = _
@FXML var pushMsat: TextField = _
@FXML var pushMsatError: Label = _
@FXML var unit: ComboBox[String] = _
@FXML var button: Button = _
@ -29,15 +31,23 @@ class OpenChannelController(val handlers: Handlers, val stage: Stage, val setup:
@FXML def handleOpen(event: ActionEvent): Unit = {
if (GUIValidators.validate(host.getText, hostError, GUIValidators.hostRegex)
&& GUIValidators.validate(amount.getText, amountError, GUIValidators.amountRegex)) {
val raw = amount.getText.toLong
val smartAmount = unit.getValue match {
case "milliBTC" => Satoshi(raw * 100000L)
case "Satoshi" => Satoshi(raw)
case "milliSatoshi" => Satoshi(raw / 1000L)
&& GUIValidators.validate(fundingSatoshis.getText, fundingSatoshisError, GUIValidators.amountRegex)) {
val rawFunding = fundingSatoshis.getText.toLong
val smartFunding = unit.getValue match {
case "milliBTC" => Satoshi(rawFunding * 100000L)
case "Satoshi" => Satoshi(rawFunding)
case "milliSatoshi" => Satoshi(rawFunding / 1000L)
}
if (!pushMsat.getText.isEmpty) {
// pushMsat is optional, so we validate field only if it isn't empty
if (GUIValidators.validate(pushMsat.getText, pushMsatError, GUIValidators.amountRegex)) {
handlers.open(host.getText, smartFunding, MilliSatoshi(pushMsat.getText.toLong))
stage.close()
}
} else {
handlers.open(host.getText, smartFunding, Satoshi(0))
stage.close()
}
handlers.open(host.getText, smartAmount)
stage.close()
}
}

View File

@ -17,8 +17,8 @@ class OpenChannelStage(handlers: Handlers, setup: Setup) extends Stage() {
initStyle(StageStyle.DECORATED)
getIcons().add(new Image("/gui/commons/images/eclair02.png", false))
setTitle("Open a new channel")
setWidth(500)
setHeight(250)
setWidth(550)
setHeight(350)
setResizable(false)
// get fxml/controller

View File

@ -4,13 +4,13 @@ import java.net.InetSocketAddress
import akka.actor._
import akka.io.{IO, Tcp}
import fr.acinq.bitcoin.{BinaryData, Satoshi}
import fr.acinq.bitcoin.{BinaryData, MilliSatoshi, Satoshi}
import fr.acinq.eclair.channel.Register.CreateChannel
/**
* Created by PM on 27/10/2015.
*/
class Client(remote: InetSocketAddress, pubkey: BinaryData, amount: Satoshi, register: ActorRef) extends Actor with ActorLogging {
class Client(remote: InetSocketAddress, pubkey: BinaryData, fundingSatoshis: Satoshi, pushMsat: MilliSatoshi, register: ActorRef) extends Actor with ActorLogging {
import Tcp._
import context.system
@ -23,15 +23,15 @@ class Client(remote: InetSocketAddress, pubkey: BinaryData, amount: Satoshi, reg
case c@Connected(remote, local) =>
log.info(s"connected to $remote")
val connection = sender()
register ! CreateChannel(connection, Some(pubkey), Some(amount))
register ! CreateChannel(connection, Some(pubkey), Some(fundingSatoshis), Some(pushMsat))
// TODO: kill this actor ?
}
}
object Client extends App {
def props(address: InetSocketAddress, pubkey: BinaryData, amount: Satoshi, register: ActorRef): Props = Props(classOf[Client], address, pubkey, amount, register)
def props(address: InetSocketAddress, pubkey: BinaryData, fundingSatoshis: Satoshi, pushMsat: MilliSatoshi, register: ActorRef): Props = Props(classOf[Client], address, pubkey, fundingSatoshis, pushMsat, register)
def props(host: String, port: Int, pubkey: BinaryData, amount: Satoshi, register: ActorRef): Props = Props(classOf[Client], new InetSocketAddress(host, port), pubkey, amount, register)
def props(host: String, port: Int, pubkey: BinaryData, fundingSatoshis: Satoshi, pushMsat: MilliSatoshi, register: ActorRef): Props = Props(classOf[Client], new InetSocketAddress(host, port), pubkey, fundingSatoshis, pushMsat, register)
}

View File

@ -28,7 +28,7 @@ class Server(address: InetSocketAddress, register: ActorRef) extends Actor with
case c@Connected(remote, local) =>
log.info(s"connected to $remote")
val connection = sender()
register ! CreateChannel(connection, None, None)
register ! CreateChannel(connection, None, None, None)
}
}