relative time locking and rbf input pane

This commit is contained in:
Craig Raw 2020-04-06 14:53:42 +02:00
parent 4d97832d97
commit 731be1a60c
6 changed files with 154 additions and 53 deletions

2
drongo

@ -1 +1 @@
Subproject commit 130fea0937629b62305892d00bc8099872259095 Subproject commit 9d15c27bfd8bd6c51f03fe081d6e70718ecf2eb0

View file

@ -189,6 +189,12 @@ public class HeadersController extends TransactionFormController implements Init
@Override @Override
public void updated(Transaction transaction) { public void updated(Transaction transaction) {
updateTxId(); updateTxId();
locktimeFieldset.setText(headersForm.getTransaction().isLocktimeSequenceEnabled() ? "Absolute Locktime" : "Absolute Locktime (sequence disabled)");
boolean locktimeEnabled = headersForm.getTransaction().isLocktimeSequenceEnabled();
locktimeNoneType.setDisable(!locktimeEnabled);
locktimeBlockType.setDisable(!locktimeEnabled);
locktimeBlock.setDisable(!locktimeEnabled);
locktimeDateType.setDisable(!locktimeEnabled);
locktimeDate.setDisable(!locktimeEnabled);
} }
} }

View file

@ -1,14 +1,12 @@
package com.sparrowwallet.sparrow.transaction; package com.sparrowwallet.sparrow.transaction;
import com.sparrowwallet.drongo.protocol.Script; import com.sparrowwallet.drongo.protocol.*;
import com.sparrowwallet.drongo.protocol.ScriptChunk;
import com.sparrowwallet.drongo.protocol.Transaction;
import com.sparrowwallet.drongo.protocol.TransactionInput;
import com.sparrowwallet.drongo.psbt.PSBTInput; import com.sparrowwallet.drongo.psbt.PSBTInput;
import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.EventManager;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
import javafx.scene.control.*; import javafx.scene.control.*;
import org.controlsfx.control.ToggleSwitch;
import org.fxmisc.richtext.CodeArea; import org.fxmisc.richtext.CodeArea;
import tornadofx.control.Field; import tornadofx.control.Field;
import tornadofx.control.Fieldset; import tornadofx.control.Fieldset;
@ -55,6 +53,12 @@ public class InputController extends TransactionFormController implements Initia
@FXML @FXML
private CodeArea witnessesArea; private CodeArea witnessesArea;
@FXML
private TextField signatures;
@FXML
private ToggleSwitch rbf;
@FXML @FXML
private ToggleGroup locktimeToggleGroup; private ToggleGroup locktimeToggleGroup;
@ -70,23 +74,20 @@ public class InputController extends TransactionFormController implements Initia
@FXML @FXML
private Fieldset locktimeFieldset; private Fieldset locktimeFieldset;
@FXML
private Field locktimeNoneField;
@FXML @FXML
private Field locktimeAbsoluteField; private Field locktimeAbsoluteField;
@FXML @FXML
private Field locktimeRelativeField; private Field locktimeRelativeField;
@FXML
private Spinner<Integer> locktimeNone;
@FXML @FXML
private TextField locktimeAbsolute; private TextField locktimeAbsolute;
@FXML @FXML
private Spinner<Integer> locktimeRelative; private Spinner<Integer> locktimeRelativeBlocks;
@FXML
private Spinner<Integer> locktimeRelativeSeconds;
@FXML @FXML
private ComboBox<String> locktimeRelativeCombo; private ComboBox<String> locktimeRelativeCombo;
@ -106,7 +107,7 @@ public class InputController extends TransactionFormController implements Initia
//TODO: Enable select outpoint when wallet present //TODO: Enable select outpoint when wallet present
outpointSelect.setDisable(true); outpointSelect.setDisable(true);
initializeScriptFields(txInput); initializeScriptFields(txInput);
initializeStatusFields(txInput);
initializeLocktimeFields(txInput); initializeLocktimeFields(txInput);
} }
@ -142,19 +143,118 @@ public class InputController extends TransactionFormController implements Initia
} }
} }
private void initializeStatusFields(TransactionInput txInput) {
Transaction transaction = inputForm.getTransaction();
signatures.setText("Unknown");
if(inputForm.getPsbtInput() != null) {
PSBTInput psbtInput = inputForm.getPsbtInput();
try {
int reqSigs = psbtInput.getSigningScript().getNumRequiredSignatures();
int foundSigs = psbtInput.getPartialSignatures().size();
signatures.setText(foundSigs + "/" + reqSigs);
} catch (NonStandardScriptException e) {
//TODO: Handle unusual transaction sig
}
}
rbf.setSelected(txInput.isReplaceByFeeEnabled());
rbf.selectedProperty().addListener((observable, oldValue, newValue) -> {
if(newValue) {
if(txInput.isAbsoluteTimeLockDisabled()) {
locktimeToggleGroup.selectToggle(locktimeAbsoluteType);
} else if(txInput.isAbsoluteTimeLocked()) {
txInput.setSequenceNumber(TransactionInput.SEQUENCE_RBF_ENABLED);
EventManager.get().notify(transaction);
}
} else {
if(txInput.isAbsoluteTimeLocked()) {
txInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED - 1);
EventManager.get().notify(transaction);
} else if(txInput.isRelativeTimeLocked()) {
locktimeToggleGroup.selectToggle(locktimeAbsoluteType);
}
}
});
}
private void initializeLocktimeFields(TransactionInput txInput) { private void initializeLocktimeFields(TransactionInput txInput) {
Transaction transaction = inputForm.getTransaction(); Transaction transaction = inputForm.getTransaction();
locktimeToggleGroup.selectedToggleProperty().addListener((ov, old_toggle, new_toggle) -> { locktimeToggleGroup.selectedToggleProperty().addListener((ov, old_toggle, new_toggle) -> {
if(locktimeToggleGroup.getSelectedToggle() != null) { if(locktimeToggleGroup.getSelectedToggle() != null) {
String selection = locktimeToggleGroup.getSelectedToggle().getUserData().toString(); String selection = locktimeToggleGroup.getSelectedToggle().getUserData().toString();
if(selection.equals("none")) { if(selection.equals("none")) {
locktimeFieldset.getChildren().removeAll(locktimeRelativeField, locktimeAbsoluteField, locktimeNoneField); locktimeFieldset.getChildren().removeAll(locktimeRelativeField, locktimeAbsoluteField);
locktimeFieldset.getChildren().add(locktimeNoneField); locktimeFieldset.getChildren().add(locktimeAbsoluteField);
updateAbsoluteLocktimeField(transaction);
locktimeAbsoluteField.setDisable(true);
txInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED); txInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED);
rbf.setSelected(false);
EventManager.get().notify(transaction); EventManager.get().notify(transaction);
} else if(selection.equals("absolute")) { } else if(selection.equals("absolute")) {
locktimeFieldset.getChildren().removeAll(locktimeRelativeField, locktimeAbsoluteField, locktimeNoneField); locktimeFieldset.getChildren().removeAll(locktimeRelativeField, locktimeAbsoluteField);
locktimeFieldset.getChildren().add(locktimeAbsoluteField); locktimeFieldset.getChildren().add(locktimeAbsoluteField);
updateAbsoluteLocktimeField(transaction);
locktimeAbsoluteField.setDisable(false);
if(rbf.selectedProperty().getValue()) {
txInput.setSequenceNumber(TransactionInput.SEQUENCE_RBF_ENABLED);
} else {
txInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED - 1);
}
EventManager.get().notify(transaction);
} else {
locktimeFieldset.getChildren().removeAll(locktimeRelativeField, locktimeAbsoluteField);
locktimeFieldset.getChildren().add(locktimeRelativeField);
if(locktimeRelativeCombo.getValue() == null) {
locktimeRelativeCombo.getSelectionModel().select(0);
} else {
setRelativeLocktime(txInput, transaction);
}
rbf.setSelected(true);
}
}
});
locktimeRelativeBlocks.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, (int)TransactionInput.RELATIVE_TIMELOCK_VALUE_MASK, 0));
locktimeRelativeSeconds.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0,
(int)TransactionInput.RELATIVE_TIMELOCK_VALUE_MASK*TransactionInput.RELATIVE_TIMELOCK_SECONDS_INCREMENT, 0,
TransactionInput.RELATIVE_TIMELOCK_SECONDS_INCREMENT));
locktimeRelativeBlocks.managedProperty().bind(locktimeRelativeBlocks.visibleProperty());
locktimeRelativeSeconds.managedProperty().bind(locktimeRelativeSeconds.visibleProperty());
locktimeRelativeCombo.getSelectionModel().selectedItemProperty().addListener((ov, old_toggle, new_toggle) -> {
boolean blocks = locktimeRelativeCombo.getValue().equals("blocks");
locktimeRelativeSeconds.setVisible(!blocks);
locktimeRelativeBlocks.setVisible(blocks);
setRelativeLocktime(txInput, transaction);
});
locktimeRelativeType.setDisable(!transaction.isRelativeLocktimeAllowed());
if(txInput.isAbsoluteTimeLockDisabled()) {
locktimeToggleGroup.selectToggle(locktimeNoneType);
} else if(txInput.isAbsoluteTimeLocked()) {
locktimeToggleGroup.selectToggle(locktimeAbsoluteType);
} else {
if(txInput.isRelativeTimeLockedInBlocks()) {
locktimeRelativeBlocks.valueFactoryProperty().get().setValue((int)txInput.getRelativeLocktime());
locktimeRelativeCombo.getSelectionModel().select(0);
} else {
locktimeRelativeSeconds.valueFactoryProperty().get().setValue((int)txInput.getRelativeLocktime() * TransactionInput.RELATIVE_TIMELOCK_SECONDS_INCREMENT);
locktimeRelativeCombo.getSelectionModel().select(1);
}
locktimeToggleGroup.selectToggle(locktimeRelativeType);
}
locktimeRelativeBlocks.valueProperty().addListener((obs, oldValue, newValue) -> {
setRelativeLocktime(txInput, transaction);
});
locktimeRelativeSeconds.valueProperty().addListener((obs, oldValue, newValue) -> {
setRelativeLocktime(txInput, transaction);
});
}
private void updateAbsoluteLocktimeField(Transaction transaction) {
long locktime = transaction.getLocktime(); long locktime = transaction.getLocktime();
if(locktime < Transaction.MAX_BLOCK_LOCKTIME) { if(locktime < Transaction.MAX_BLOCK_LOCKTIME) {
locktimeAbsoluteField.setText("Block:"); locktimeAbsoluteField.setText("Block:");
@ -164,49 +264,16 @@ public class InputController extends TransactionFormController implements Initia
LocalDateTime localDateTime = Instant.ofEpochSecond(locktime).atZone(ZoneId.systemDefault()).toLocalDateTime(); LocalDateTime localDateTime = Instant.ofEpochSecond(locktime).atZone(ZoneId.systemDefault()).toLocalDateTime();
locktimeAbsolute.setText(DateTimeFormatter.ofPattern(HeadersController.LOCKTIME_DATE_FORMAT).format(localDateTime)); locktimeAbsolute.setText(DateTimeFormatter.ofPattern(HeadersController.LOCKTIME_DATE_FORMAT).format(localDateTime));
} }
//TODO: Check RBF field and set appropriately
txInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED - 1);
EventManager.get().notify(transaction);
} else {
locktimeFieldset.getChildren().removeAll(locktimeRelativeField, locktimeAbsoluteField, locktimeNoneField);
locktimeFieldset.getChildren().add(locktimeRelativeField);
setRelativeLocktime(txInput, transaction, locktimeRelative.getValue());
}
}
});
locktimeNone.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, (int)Transaction.MAX_BLOCK_LOCKTIME-1, (int)transaction.getLocktime()));
locktimeRelativeCombo.getSelectionModel().select(0);
locktimeRelative.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, (int)TransactionInput.MAX_RELATIVE_TIMELOCK_IN_BLOCKS, 0));
if(txInput.isAbsoluteTimeLockDisabled()) {
locktimeToggleGroup.selectToggle(locktimeNoneType);
} else if(txInput.isAbsoluteTimeLocked()) {
locktimeToggleGroup.selectToggle(locktimeAbsoluteType);
} else {
locktimeRelative.valueFactoryProperty().get().setValue((int)txInput.getRelativeLocktime());
if(txInput.isRelativeTimeLockedInBlocks()) {
locktimeRelativeCombo.getSelectionModel().select(0);
} else {
locktimeRelativeCombo.getSelectionModel().select(1);
}
locktimeToggleGroup.selectToggle(locktimeRelativeType);
} }
locktimeRelative.valueProperty().addListener((obs, oldValue, newValue) -> { private void setRelativeLocktime(TransactionInput txInput, Transaction transaction) {
setRelativeLocktime(txInput, transaction, newValue);
});
locktimeRelativeCombo.getSelectionModel().selectedItemProperty().addListener((ov, old_toggle, new_toggle) -> {
setRelativeLocktime(txInput, transaction, locktimeRelative.getValue());
});
}
private void setRelativeLocktime(TransactionInput txInput, Transaction transaction, Integer value) {
String relativeSelection = locktimeRelativeCombo.getValue(); String relativeSelection = locktimeRelativeCombo.getValue();
if (relativeSelection.equals("blocks")) { if(relativeSelection.equals("blocks")) {
txInput.setSequenceNumber(value & 0xFFFF); Integer value = locktimeRelativeBlocks.getValue();
txInput.setSequenceNumber(value & TransactionInput.RELATIVE_TIMELOCK_VALUE_MASK);
} else { } else {
txInput.setSequenceNumber((value & 0xFFFF) | 0x400000); Integer value = locktimeRelativeSeconds.getValue() / TransactionInput.RELATIVE_TIMELOCK_SECONDS_INCREMENT;
txInput.setSequenceNumber((value & TransactionInput.RELATIVE_TIMELOCK_VALUE_MASK) | TransactionInput.RELATIVE_TIMELOCK_TYPE_FLAG);
} }
EventManager.get().notify(transaction); EventManager.get().notify(transaction);
} }

View file

@ -106,7 +106,7 @@ public abstract class TransactionFormController {
Popup popup = new Popup(); Popup popup = new Popup();
Label popupMsg = new Label(); Label popupMsg = new Label();
popupMsg.setStyle("-fx-background-color: #696c77; -fx-text-fill: white; -fx-padding: 5;"); popupMsg.getStyleClass().add("tooltip");
popup.getContent().add(popupMsg); popup.getContent().add(popupMsg);
area.setMouseOverTextDelay(Duration.ofMillis(150)); area.setMouseOverTextDelay(Duration.ofMillis(150));

View file

@ -21,3 +21,9 @@
.form-separator { .form-separator {
-fx-padding: -15 0 0 0; -fx-padding: -15 0 0 0;
} }
.toggle-switch {
-fx-translate-x: -20px;
}

View file

@ -10,6 +10,7 @@
<?import org.controlsfx.control.SegmentedButton?> <?import org.controlsfx.control.SegmentedButton?>
<?import javafx.collections.FXCollections?> <?import javafx.collections.FXCollections?>
<?import java.lang.String?> <?import java.lang.String?>
<?import org.controlsfx.control.ToggleSwitch?>
<GridPane alignment="TOP_CENTER" hgap="10.0" prefHeight="500.0" prefWidth="620.0" stylesheets="@input.css, @../general.css" vgap="10.0" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.transaction.InputController"> <GridPane alignment="TOP_CENTER" hgap="10.0" prefHeight="500.0" prefWidth="620.0" stylesheets="@input.css, @../general.css" vgap="10.0" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.transaction.InputController">
<padding> <padding>
@ -83,7 +84,18 @@
<Separator styleClass="form-separator" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="3" /> <Separator styleClass="form-separator" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="3" />
<Form GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="4"> <Form GridPane.columnIndex="0" GridPane.rowIndex="4">
<Fieldset text="Status" inputGrow="SOMETIMES">
<Field text="Signatures:">
<TextField fx:id="signatures" editable="false" prefWidth="120" styleClass="copyable-label"/>
</Field>
<Field text="RBF:">
<ToggleSwitch fx:id="rbf"/>
</Field>
</Fieldset>
</Form>
<Form GridPane.columnIndex="1" GridPane.rowIndex="4">
<Fieldset fx:id="locktimeFieldset" text="Locktime" inputGrow="SOMETIMES"> <Fieldset fx:id="locktimeFieldset" text="Locktime" inputGrow="SOMETIMES">
<Field text="Type:"> <Field text="Type:">
<SegmentedButton> <SegmentedButton>
@ -91,20 +103,30 @@
<ToggleGroup fx:id="locktimeToggleGroup" /> <ToggleGroup fx:id="locktimeToggleGroup" />
</toggleGroup> </toggleGroup>
<buttons> <buttons>
<ToggleButton fx:id="locktimeNoneType" text="Disabled" userData="none" toggleGroup="$locktimeToggleGroup" /> <ToggleButton fx:id="locktimeNoneType" text="Disabled" userData="none" toggleGroup="$locktimeToggleGroup">
<ToggleButton fx:id="locktimeAbsoluteType" text="Absolute" userData="absolute" toggleGroup="$locktimeToggleGroup" /> <tooltip>
<ToggleButton fx:id="locktimeRelativeType" text="Relative" userData="relative" toggleGroup="$locktimeToggleGroup" /> <Tooltip text="Disable time locks"/>
</tooltip>
</ToggleButton>
<ToggleButton fx:id="locktimeAbsoluteType" text="Absolute" userData="absolute" toggleGroup="$locktimeToggleGroup">
<tooltip>
<Tooltip text="Enable absolute (transaction level) time lock only"/>
</tooltip>
</ToggleButton>
<ToggleButton fx:id="locktimeRelativeType" text="Relative" userData="relative" toggleGroup="$locktimeToggleGroup">
<tooltip>
<Tooltip text="Enable both absolute and relative time locks"/>
</tooltip>
</ToggleButton>
</buttons> </buttons>
</SegmentedButton> </SegmentedButton>
</Field> </Field>
<Field fx:id="locktimeNoneField" text="Block:">
<Spinner fx:id="locktimeNone" disable="true" prefWidth="90"/>
</Field>
<Field fx:id="locktimeAbsoluteField" text="Block:"> <Field fx:id="locktimeAbsoluteField" text="Block:">
<TextField fx:id="locktimeAbsolute" editable="false" prefWidth="120" styleClass="copyable-label"/> <TextField fx:id="locktimeAbsolute" editable="false" prefWidth="120" styleClass="copyable-label"/>
</Field> </Field>
<Field fx:id="locktimeRelativeField" text="Block:"> <Field fx:id="locktimeRelativeField" text="Value:">
<Spinner fx:id="locktimeRelative" editable="true" prefWidth="90" prefHeight="25"/> <Spinner fx:id="locktimeRelativeBlocks" editable="true" prefWidth="110"/>
<Spinner fx:id="locktimeRelativeSeconds" editable="true" prefWidth="110"/>
<ComboBox fx:id="locktimeRelativeCombo"> <ComboBox fx:id="locktimeRelativeCombo">
<items> <items>
<FXCollections fx:factory="observableArrayList"> <FXCollections fx:factory="observableArrayList">