mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2024-11-19 09:52:09 +01:00
Populate Broadcast DLC GUI (#3260)
This commit is contained in:
parent
eeb4741d60
commit
935354993b
@ -1896,11 +1896,13 @@ object CliCommand {
|
||||
|
||||
case class AddDLCSigsFromFile(path: Path) extends AddDLCSigsCliCommand
|
||||
|
||||
sealed trait AddDLCSigsAndBroadcastCliCommand extends AddDLCSigsCliCommand
|
||||
|
||||
case class AddDLCSigsAndBroadcast(sigs: LnMessage[DLCSignTLV])
|
||||
extends AddDLCSigsCliCommand
|
||||
extends AddDLCSigsAndBroadcastCliCommand
|
||||
|
||||
case class AddDLCSigsAndBroadcastFromFile(path: Path)
|
||||
extends AddDLCSigsCliCommand
|
||||
extends AddDLCSigsAndBroadcastCliCommand
|
||||
|
||||
case class GetDLCFundingTx(contractId: ByteVector) extends AppServerCliCommand
|
||||
|
||||
|
@ -6,9 +6,9 @@ import org.bitcoins.cli.{CliCommand, ConsoleCli}
|
||||
import org.bitcoins.commons.serializers.Picklers._
|
||||
import org.bitcoins.core.protocol.dlc.models._
|
||||
import org.bitcoins.crypto._
|
||||
import org.bitcoins.gui._
|
||||
import org.bitcoins.gui.dlc.GlobalDLCData.dlcs
|
||||
import org.bitcoins.gui.dlc.dialog._
|
||||
import org.bitcoins.gui._
|
||||
import scalafx.application.Platform
|
||||
import scalafx.beans.property.ObjectProperty
|
||||
import scalafx.scene.control.Alert.AlertType
|
||||
@ -158,12 +158,28 @@ class DLCPaneModel(val resultArea: TextArea) extends Logging {
|
||||
}
|
||||
|
||||
def onBroadcastDLC(): Unit = {
|
||||
val processStr: String => String = str => {
|
||||
if (str.isEmpty) {
|
||||
"Broadcasting DLC timed out! Try again in a bit."
|
||||
} else str
|
||||
val result = BroadcastDLCDialog.showAndWait(parentWindow.value)
|
||||
|
||||
result match {
|
||||
case Some(command) =>
|
||||
taskRunner.run(
|
||||
caption = "Broadcast DLC",
|
||||
op = {
|
||||
ConsoleCli.exec(command, GlobalData.consoleCliConfig) match {
|
||||
case Success(commandReturn) =>
|
||||
val string = if (commandReturn.isEmpty) {
|
||||
"Broadcasting DLC timed out! Try again in a bit."
|
||||
} else commandReturn
|
||||
resultArea.text = string
|
||||
case Failure(err) =>
|
||||
err.printStackTrace()
|
||||
resultArea.text = s"Error executing command:\n${err.getMessage}"
|
||||
}
|
||||
updateDLCs()
|
||||
}
|
||||
)
|
||||
case None => ()
|
||||
}
|
||||
printDLCDialogResult("Broadcast DLC", new BroadcastDLCDialog, processStr)
|
||||
}
|
||||
|
||||
def onExecute(): Unit = {
|
||||
|
@ -1,47 +1,298 @@
|
||||
package org.bitcoins.gui.dlc.dialog
|
||||
|
||||
import grizzled.slf4j.Logging
|
||||
import org.bitcoins.cli.CliCommand._
|
||||
import org.bitcoins.core.protocol.dlc.models.DLCStatus.getContractId
|
||||
import org.bitcoins.core.protocol.dlc.models._
|
||||
import org.bitcoins.core.protocol.tlv._
|
||||
import org.bitcoins.gui.dlc.GlobalDLCData
|
||||
import scalafx.scene.Node
|
||||
import org.bitcoins.gui.GlobalData
|
||||
import org.bitcoins.gui.dlc.DLCPlotUtil
|
||||
import org.bitcoins.gui.dlc.GlobalDLCData.dlcs
|
||||
import org.bitcoins.gui.util.GUIUtil
|
||||
import scalafx.Includes._
|
||||
import scalafx.geometry.{Insets, Pos}
|
||||
import scalafx.scene.control._
|
||||
import scalafx.scene.layout._
|
||||
import scalafx.stage.Window
|
||||
|
||||
class BroadcastDLCDialog
|
||||
extends DLCDialog[AddDLCSigsCliCommand]("Add DLC Signatures",
|
||||
"Enter DLC signatures message",
|
||||
Vector(
|
||||
DLCDialog.dlcSigStr -> DLCDialog
|
||||
.textArea(),
|
||||
DLCDialog.dlcSignFileStr ->
|
||||
DLCDialog.fileChooserButton(
|
||||
open = true,
|
||||
{ file =>
|
||||
DLCDialog.signDLCFile =
|
||||
Some(file)
|
||||
DLCDialog.signFileChosenLabel.text =
|
||||
file.toString
|
||||
}),
|
||||
DLCDialog.fileChosenStr -> DLCDialog.signFileChosenLabel
|
||||
),
|
||||
Vector(DLCDialog.dlcSigStr,
|
||||
DLCDialog.dlcSignFileStr)) {
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
import scala.collection._
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
||||
import DLCDialog._
|
||||
object BroadcastDLCDialog extends Logging {
|
||||
|
||||
override def constructFromInput(
|
||||
inputs: Map[String, Node]): AddDLCSigsCliCommand = {
|
||||
signDLCFile match {
|
||||
case Some(file) =>
|
||||
signDLCFile = None // reset
|
||||
signFileChosenLabel.text = "" // reset
|
||||
AddDLCSigsAndBroadcastFromFile(file.toPath)
|
||||
case None =>
|
||||
val signHex = readStringFromNode(inputs(dlcSigStr))
|
||||
def showAndWait(
|
||||
parentWindow: Window): Option[AddDLCSigsAndBroadcastCliCommand] = {
|
||||
val dialog = new Dialog[Option[AddDLCSigsAndBroadcastCliCommand]]() {
|
||||
initOwner(parentWindow)
|
||||
title = "Add DLC Signatures"
|
||||
headerText = "Enter DLC signatures message"
|
||||
}
|
||||
|
||||
val sign = LnMessageFactory(DLCSignTLV).fromHex(signHex)
|
||||
dialog.dialogPane().buttonTypes = Seq(ButtonType.OK, ButtonType.Cancel)
|
||||
dialog.dialogPane().stylesheets = GlobalData.currentStyleSheets
|
||||
dialog.resizable = true
|
||||
|
||||
GlobalDLCData.lastContractId = sign.tlv.contractId.toHex
|
||||
var dlcDetailsShown = false
|
||||
val signTLVTF = new TextField() {
|
||||
minWidth = 300
|
||||
}
|
||||
|
||||
AddDLCSigsAndBroadcast(sign)
|
||||
val signTFHBox = new HBox() {
|
||||
spacing = 5
|
||||
children = Vector(new Label("DLC Signatures"), signTLVTF)
|
||||
}
|
||||
|
||||
val separatorHBox = new HBox() {
|
||||
alignment = Pos.Center
|
||||
alignmentInParent = Pos.Center
|
||||
spacing = 5
|
||||
minWidth <== signTFHBox.width
|
||||
children = Vector(new Separator(), new Label("or"), new Separator())
|
||||
}
|
||||
|
||||
val errorLabel = new Label("") {
|
||||
style = "-fx-text-fill: red"
|
||||
}
|
||||
|
||||
val fromFileHBox = new HBox() {
|
||||
spacing = 5
|
||||
children = Vector(new Label("DLC Sign File"))
|
||||
}
|
||||
|
||||
val vbox = new VBox() {
|
||||
margin = Insets(10)
|
||||
spacing = 10
|
||||
children = Vector(signTFHBox, separatorHBox, fromFileHBox)
|
||||
}
|
||||
|
||||
var nextRow: Int = 2
|
||||
val gridPane = new GridPane {
|
||||
alignment = Pos.Center
|
||||
padding = Insets(10)
|
||||
hgap = 5
|
||||
vgap = 5
|
||||
}
|
||||
|
||||
def showDLCTerms(status: DLCStatus, isFromFile: Boolean): Unit = {
|
||||
vbox.children.clear()
|
||||
if (isFromFile) {
|
||||
vbox.children.add(fromFileHBox)
|
||||
} else {
|
||||
vbox.children.addAll(signTFHBox)
|
||||
}
|
||||
vbox.children.add(gridPane)
|
||||
|
||||
val (oracleKey, eventId) = status.contractInfo.oracleInfo.toTLV match {
|
||||
case OracleInfoV0TLV(announcement) =>
|
||||
(announcement.publicKey.hex, announcement.eventTLV.eventId)
|
||||
case _: MultiOracleInfoTLV =>
|
||||
throw new RuntimeException("This is impossible.")
|
||||
}
|
||||
|
||||
gridPane.add(new Label("Event Id"), 0, nextRow)
|
||||
gridPane.add(
|
||||
new TextField() {
|
||||
text = eventId
|
||||
editable = false
|
||||
minWidth = 300
|
||||
},
|
||||
1,
|
||||
nextRow
|
||||
)
|
||||
nextRow += 1
|
||||
|
||||
gridPane.add(new Label("Oracle Public Key"), 0, nextRow)
|
||||
gridPane.add(
|
||||
new TextField() {
|
||||
text = oracleKey
|
||||
editable = false
|
||||
},
|
||||
1,
|
||||
nextRow
|
||||
)
|
||||
nextRow += 1
|
||||
|
||||
gridPane.add(new Label("Your Collateral"), 0, nextRow)
|
||||
gridPane.add(new TextField() {
|
||||
text = status.localCollateral.satoshis.toString
|
||||
editable = false
|
||||
},
|
||||
1,
|
||||
nextRow)
|
||||
nextRow += 1
|
||||
|
||||
gridPane.add(new Label("Counter Party Collateral"), 0, nextRow)
|
||||
gridPane.add(new TextField() {
|
||||
text = status.remoteCollateral.satoshis.toString
|
||||
editable = false
|
||||
},
|
||||
1,
|
||||
nextRow)
|
||||
nextRow += 1
|
||||
|
||||
status.contractInfo.contractDescriptor.toTLV match {
|
||||
case v0: ContractDescriptorV0TLV =>
|
||||
gridPane.add(new Label("Potential Outcome"), 0, nextRow)
|
||||
gridPane.add(new Label("Payouts"), 1, nextRow)
|
||||
nextRow += 1
|
||||
|
||||
val descriptor = EnumContractDescriptor
|
||||
.fromTLV(v0)
|
||||
.flip(status.totalCollateral.satoshis)
|
||||
|
||||
descriptor.foreach { case (str, satoshis) =>
|
||||
gridPane.add(new TextField() {
|
||||
text = str.outcome
|
||||
editable = false
|
||||
},
|
||||
0,
|
||||
nextRow)
|
||||
gridPane.add(new TextField() {
|
||||
text = satoshis.toString
|
||||
editable = false
|
||||
},
|
||||
1,
|
||||
nextRow)
|
||||
nextRow += 1
|
||||
}
|
||||
case v1: ContractDescriptorV1TLV =>
|
||||
val previewGraphButton: Button = new Button("Preview Graph") {
|
||||
onAction = _ => {
|
||||
val descriptor = NumericContractDescriptor.fromTLV(v1)
|
||||
val payoutCurve = if (status.isInitiator) {
|
||||
descriptor.outcomeValueFunc
|
||||
} else {
|
||||
descriptor
|
||||
.flip(status.totalCollateral.satoshis)
|
||||
.outcomeValueFunc
|
||||
}
|
||||
|
||||
DLCPlotUtil.plotCETs(base = 2,
|
||||
descriptor.numDigits,
|
||||
payoutCurve,
|
||||
status.contractInfo.totalCollateral,
|
||||
descriptor.roundingIntervals,
|
||||
None)
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
gridPane.add(new Label("Payout Function"), 0, nextRow)
|
||||
gridPane.add(previewGraphButton, 1, nextRow)
|
||||
nextRow += 1
|
||||
}
|
||||
|
||||
gridPane.add(new Label("Fee Rate"), 0, nextRow)
|
||||
gridPane.add(new TextField() {
|
||||
text = status.feeRate.toString
|
||||
editable = false
|
||||
},
|
||||
1,
|
||||
nextRow)
|
||||
nextRow += 1
|
||||
|
||||
gridPane.add(new Label("Refund Date"), 0, nextRow)
|
||||
gridPane.add(
|
||||
new TextField() {
|
||||
text = GUIUtil.epochToDateString(status.timeouts.contractTimeout)
|
||||
editable = false
|
||||
},
|
||||
1,
|
||||
nextRow)
|
||||
nextRow += 1
|
||||
}
|
||||
|
||||
signTLVTF.onKeyTyped = _ => {
|
||||
if (!dlcDetailsShown) {
|
||||
Try(LnMessageFactory(DLCSignTLV).fromHex(signTLVTF.text.value)) match {
|
||||
case Failure(_) => ()
|
||||
case Success(lnMessage) =>
|
||||
showDetails(lnMessage, isFromFile = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def showDetails(
|
||||
lnMessage: LnMessage[DLCSignTLV],
|
||||
isFromFile: Boolean): Unit = {
|
||||
val tempId = lnMessage.tlv.contractId
|
||||
dlcs.find(getContractId(_).contains(tempId)) match {
|
||||
case Some(dlc) =>
|
||||
dlcDetailsShown = true
|
||||
showDLCTerms(dlc, isFromFile)
|
||||
case None =>
|
||||
val errMsg =
|
||||
s"DLCSign is not associated with a DLC in our DLC Table View"
|
||||
logger.error(errMsg)
|
||||
errorLabel.text = errMsg
|
||||
vbox.children.add(errorLabel)
|
||||
}
|
||||
dialog.dialogPane().getScene.getWindow.sizeToScene()
|
||||
}
|
||||
|
||||
def handleFileChosen(file: File): Unit = {
|
||||
val signMessageT = Try {
|
||||
val hex = Files.readAllLines(file.toPath).get(0)
|
||||
LnMessageFactory(DLCSignTLV).fromHex(hex)
|
||||
}
|
||||
|
||||
signMessageT match {
|
||||
case Failure(_) =>
|
||||
val errMsg = "Error, file chosen as not a valid DLCSign message"
|
||||
logger.error(errMsg)
|
||||
errorLabel.text = errMsg
|
||||
vbox.children.add(errorLabel)
|
||||
dialog.dialogPane().getScene.getWindow.sizeToScene()
|
||||
case Success(sign) =>
|
||||
showDetails(sign, isFromFile = true)
|
||||
}
|
||||
()
|
||||
}
|
||||
|
||||
val fileChooser = DLCDialog.fileChooserButton(
|
||||
open = true,
|
||||
{ file =>
|
||||
DLCDialog.signDLCFile = Some(file)
|
||||
DLCDialog.signFileChosenLabel.text = file.toString
|
||||
handleFileChosen(file)
|
||||
})
|
||||
|
||||
fromFileHBox.children.addAll(fileChooser, DLCDialog.signFileChosenLabel)
|
||||
|
||||
dialog.dialogPane().content = new ScrollPane {
|
||||
margin = Insets(10)
|
||||
content = vbox
|
||||
}
|
||||
|
||||
// When the OK button is clicked, convert the result to a SignDLC.
|
||||
dialog.resultConverter = dialogButton => {
|
||||
val res = if (dialogButton == ButtonType.OK) {
|
||||
DLCDialog.signDLCFile match {
|
||||
case Some(file) =>
|
||||
Some(AddDLCSigsAndBroadcastFromFile(file.toPath))
|
||||
case None =>
|
||||
val hex = signTLVTF.text.value
|
||||
val signTLV = LnMessageFactory(DLCSignTLV)(hex)
|
||||
|
||||
Some(AddDLCSigsAndBroadcast(signTLV))
|
||||
}
|
||||
} else None
|
||||
|
||||
// reset
|
||||
DLCDialog.signDLCFile = None
|
||||
DLCDialog.signFileChosenLabel.text = ""
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
val result = dialog.showAndWait()
|
||||
|
||||
result match {
|
||||
case Some(Some(cmd: AddDLCSigsAndBroadcastCliCommand)) =>
|
||||
Some(cmd)
|
||||
case Some(_) | None => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user