tx creation support

This commit is contained in:
Craig Raw 2020-07-04 12:52:40 +02:00
parent 013ed89e98
commit 48e741733b
5 changed files with 82 additions and 52 deletions

2
drongo

@ -1 +1 @@
Subproject commit 3ee7cd11eb31da06d79132f0023e6da7e534906d Subproject commit ccf7de9f625c4cc73efc6948b3e699a7786da276

View file

@ -4,12 +4,13 @@ import com.sparrowwallet.drongo.address.Address;
import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex; import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex;
import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.drongo.wallet.WalletNode; import com.sparrowwallet.drongo.wallet.WalletNode;
import com.sparrowwallet.drongo.wallet.WalletTransaction;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.layout.*; import javafx.scene.layout.*;
import java.util.Collection; import java.util.Map;
public class TransactionDiagram extends GridPane { public class TransactionDiagram extends GridPane {
public TransactionDiagram() { public TransactionDiagram() {
@ -23,25 +24,34 @@ public class TransactionDiagram extends GridPane {
} }
} }
public void update(Wallet wallet, Collection<BlockTransactionHashIndex> inputs, Address toAddress, WalletNode changeNode, long fee) { public void update(WalletTransaction walletTx) {
Pane inputsPane = getInputsLabels(inputs); if(walletTx == null) {
getChildren().clear();
} else {
update(walletTx.getWallet(), walletTx.getSelectedUtxos(), walletTx.getRecipientAddress(), walletTx.getChangeNode(), walletTx.getFee());
}
}
public void update(Wallet wallet, Map<BlockTransactionHashIndex, WalletNode> selectedUtxos, Address toAddress, WalletNode changeNode, long fee) {
Pane inputsPane = getInputsLabels(selectedUtxos);
GridPane.setConstraints(inputsPane, 0, 0); GridPane.setConstraints(inputsPane, 0, 0);
Pane txPane = getTransactionPane(); Pane txPane = getTransactionPane();
GridPane.setConstraints(inputsPane, 2, 0); GridPane.setConstraints(txPane, 2, 0);
Pane outputsPane = getOutputsLabels(wallet, toAddress, changeNode, fee); Pane outputsPane = getOutputsLabels(wallet, toAddress, changeNode, fee);
GridPane.setConstraints(inputsPane, 4, 0); GridPane.setConstraints(outputsPane, 4, 0);
getChildren().clear(); getChildren().clear();
getChildren().addAll(inputsPane, txPane, outputsPane); getChildren().addAll(inputsPane, txPane, outputsPane);
} }
private Pane getInputsLabels(Collection<BlockTransactionHashIndex> inputs) { private Pane getInputsLabels(Map<BlockTransactionHashIndex, WalletNode> selectedUtxos) {
VBox inputsBox = new VBox(); VBox inputsBox = new VBox();
inputsBox.minHeightProperty().bind(minHeightProperty());
inputsBox.setAlignment(Pos.CENTER_RIGHT); inputsBox.setAlignment(Pos.CENTER_RIGHT);
inputsBox.getChildren().add(createSpacer()); inputsBox.getChildren().add(createSpacer());
for(BlockTransactionHashIndex input : inputs) { for(BlockTransactionHashIndex input : selectedUtxos.keySet()) {
String desc = input.getLabel() != null && !input.getLabel().isEmpty() ? input.getLabel() : input.getHashAsString().substring(0, 8) + "...:" + input.getIndex(); String desc = input.getLabel() != null && !input.getLabel().isEmpty() ? input.getLabel() : input.getHashAsString().substring(0, 8) + "...:" + input.getIndex();
Label label = new Label(desc); Label label = new Label(desc);
inputsBox.getChildren().add(label); inputsBox.getChildren().add(label);

View file

@ -3,18 +3,18 @@ package com.sparrowwallet.sparrow.wallet;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import com.sparrowwallet.drongo.address.Address; import com.sparrowwallet.drongo.address.Address;
import com.sparrowwallet.drongo.address.InvalidAddressException; import com.sparrowwallet.drongo.address.InvalidAddressException;
import com.sparrowwallet.drongo.protocol.Transaction;
import com.sparrowwallet.drongo.wallet.*; import com.sparrowwallet.drongo.wallet.*;
import com.sparrowwallet.sparrow.AppController; import com.sparrowwallet.sparrow.AppController;
import com.sparrowwallet.sparrow.BitcoinUnit; import com.sparrowwallet.sparrow.BitcoinUnit;
import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.control.CopyableLabel; import com.sparrowwallet.sparrow.control.*;
import com.sparrowwallet.sparrow.control.CopyableTextField;
import com.sparrowwallet.sparrow.control.FeeRatesChart;
import com.sparrowwallet.sparrow.control.TextFieldValidator;
import com.sparrowwallet.sparrow.event.FeeRatesUpdatedEvent; import com.sparrowwallet.sparrow.event.FeeRatesUpdatedEvent;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
@ -27,11 +27,16 @@ import org.controlsfx.validation.Validator;
import org.controlsfx.validation.decoration.StyleClassValidationDecoration; import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
import java.net.URL; import java.net.URL;
import java.util.*; import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.stream.Collectors;
public class SendController extends WalletFormController implements Initializable { public class SendController extends WalletFormController implements Initializable {
public static final List<Integer> TARGET_BLOCKS_RANGE = List.of(1, 2, 3, 4, 5, 10, 25, 50); public static final List<Integer> TARGET_BLOCKS_RANGE = List.of(1, 2, 3, 4, 5, 10, 25, 50);
public static final double FALLBACK_FEE_RATE = 20000d / 1000;
@FXML @FXML
private CopyableTextField address; private CopyableTextField address;
@ -60,15 +65,15 @@ public class SendController extends WalletFormController implements Initializabl
private FeeRatesChart feeRatesChart; private FeeRatesChart feeRatesChart;
@FXML @FXML
private Button clear; private TransactionDiagram transactionDiagram;
@FXML @FXML
private Button select; private Button clear;
@FXML @FXML
private Button create; private Button create;
private ObservableList<BlockTransactionHashIndex> inputs; private final ObjectProperty<WalletTransaction> walletTransactionProperty = new SimpleObjectProperty<>(null);
private final BooleanProperty insufficientInputsProperty = new SimpleBooleanProperty(false); private final BooleanProperty insufficientInputsProperty = new SimpleBooleanProperty(false);
@ -144,19 +149,16 @@ public class SendController extends WalletFormController implements Initializabl
updateTransaction(); updateTransaction();
}); });
select.managedProperty().bind(select.visibleProperty()); walletTransactionProperty.addListener((observable, oldValue, newValue) -> {
create.managedProperty().bind(create.visibleProperty()); transactionDiagram.update(newValue);
if(inputs == null || inputs.isEmpty()) { create.setDisable(false);
create.setVisible(false); });
} else {
select.setVisible(false);
}
} }
private void addValidation() { private void addValidation() {
ValidationSupport validationSupport = new ValidationSupport(); ValidationSupport validationSupport = new ValidationSupport();
validationSupport.registerValidator(address, Validator.combine( validationSupport.registerValidator(address, Validator.combine(
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid Address", !isValidAddress()) (Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid Address", !newValue.isEmpty() && !isValidAddress())
)); ));
validationSupport.registerValidator(amount, Validator.combine( validationSupport.registerValidator(amount, Validator.combine(
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Insufficient Inputs", insufficientInputsProperty.get()) (Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Insufficient Inputs", insufficientInputsProperty.get())
@ -167,32 +169,22 @@ public class SendController extends WalletFormController implements Initializabl
private void updateTransaction() { private void updateTransaction() {
try { try {
Address address = getAddress(); Address recipientAddress = getAddress();
Long amount = getAmount(); Long recipientAmount = getAmount();
if(amount != null) { if(recipientAmount != null) {
Collection<BlockTransactionHashIndex> selectedInputs = selectInputs(amount); Wallet wallet = getWalletForm().getWallet();
WalletTransaction walletTransaction = wallet.createWalletTransaction(getUtxoSelectors(), recipientAddress, recipientAmount, getFeeRate());
Transaction transaction = new Transaction(); walletTransactionProperty.setValue(walletTransaction);
insufficientInputsProperty.set(false);
return;
} }
} catch (InvalidAddressException e) { } catch (InvalidAddressException e) {
//ignore //ignore
} catch (InvalidTransactionException.InsufficientInputsException e) { } catch (InsufficientFundsException e) {
insufficientInputsProperty.set(true); insufficientInputsProperty.set(true);
} }
}
private Collection<BlockTransactionHashIndex> selectInputs(Long targetValue) throws InvalidTransactionException.InsufficientInputsException { walletTransactionProperty.setValue(null);
Set<BlockTransactionHashIndex> utxos = getWalletForm().getWallet().getWalletUtxos().keySet();
for(UtxoSelector utxoSelector : getUtxoSelectors()) {
Collection<BlockTransactionHashIndex> selectedInputs = utxoSelector.select(targetValue, utxos);
long total = selectedInputs.stream().mapToLong(BlockTransactionHashIndex::getValue).sum();
if(total > targetValue) {
return selectedInputs;
}
}
throw new InvalidTransactionException.InsufficientInputsException("Not enough inputs for output value " + targetValue);
} }
private List<UtxoSelector> getUtxoSelectors() { private List<UtxoSelector> getUtxoSelectors() {
@ -225,8 +217,8 @@ public class SendController extends WalletFormController implements Initializabl
} }
private Long getFee() { private Long getFee() {
BitcoinUnit bitcoinUnit = amountUnit.getSelectionModel().getSelectedItem(); BitcoinUnit bitcoinUnit = feeAmountUnit.getSelectionModel().getSelectedItem();
if(amount.getText() != null && !amount.getText().isEmpty()) { if(fee.getText() != null && !fee.getText().isEmpty()) {
Double fieldValue = Double.parseDouble(amount.getText()); Double fieldValue = Double.parseDouble(amount.getText());
return bitcoinUnit.getSatsValue(fieldValue); return bitcoinUnit.getSatsValue(fieldValue);
} }
@ -246,7 +238,16 @@ public class SendController extends WalletFormController implements Initializabl
} }
private Map<Integer, Double> getTargetBlocksFeeRates() { private Map<Integer, Double> getTargetBlocksFeeRates() {
return AppController.getTargetBlockFeeRates(); Map<Integer, Double> retrievedFeeRates = AppController.getTargetBlockFeeRates();
if(retrievedFeeRates == null) {
retrievedFeeRates = TARGET_BLOCKS_RANGE.stream().collect(Collectors.toMap(java.util.function.Function.identity(), v -> FALLBACK_FEE_RATE));
}
return retrievedFeeRates;
}
private Double getFeeRate() {
return getTargetBlocksFeeRates().get(getTargetBlocks());
} }
private void setFeeRate(Double feeRateAmt) { private void setFeeRate(Double feeRateAmt) {
@ -257,6 +258,18 @@ public class SendController extends WalletFormController implements Initializabl
} }
public void clear(ActionEvent event) {
address.setText("");
label.setText("");
amount.setText("");
fee.setText("");
walletTransactionProperty.setValue(null);
}
public void createTransaction(ActionEvent event) {
}
@Subscribe @Subscribe
public void feeRatesUpdated(FeeRatesUpdatedEvent event) { public void feeRatesUpdated(FeeRatesUpdatedEvent event) {
feeRatesChart.update(event.getTargetBlockFeeRates()); feeRatesChart.update(event.getTargetBlockFeeRates());

View file

@ -31,3 +31,7 @@
#feeRateField .input-container { #feeRateField .input-container {
-fx-alignment: center-left; -fx-alignment: center-left;
} }
#transactionDiagram {
-fx-min-height: 230px;
}

View file

@ -14,11 +14,12 @@
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import com.sparrowwallet.sparrow.control.CopyableLabel?> <?import com.sparrowwallet.sparrow.control.CopyableLabel?>
<?import javafx.collections.FXCollections?> <?import javafx.collections.FXCollections?>
<?import com.sparrowwallet.drongo.policy.PolicyType?>
<?import com.sparrowwallet.sparrow.BitcoinUnit?> <?import com.sparrowwallet.sparrow.BitcoinUnit?>
<?import com.sparrowwallet.sparrow.control.FeeRatesChart?> <?import com.sparrowwallet.sparrow.control.FeeRatesChart?>
<?import javafx.scene.chart.CategoryAxis?> <?import javafx.scene.chart.CategoryAxis?>
<?import javafx.scene.chart.NumberAxis?> <?import javafx.scene.chart.NumberAxis?>
<?import com.sparrowwallet.sparrow.control.TransactionDiagram?>
<BorderPane stylesheets="@send.css, @wallet.css, @../script.css, @../general.css" styleClass="wallet-pane" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.wallet.SendController"> <BorderPane stylesheets="@send.css, @wallet.css, @../script.css, @../general.css" styleClass="wallet-pane" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.wallet.SendController">
<center> <center>
<GridPane styleClass="send-form" hgap="10.0" vgap="10.0"> <GridPane styleClass="send-form" hgap="10.0" vgap="10.0">
@ -87,6 +88,9 @@
</yAxis> </yAxis>
</FeeRatesChart> </FeeRatesChart>
</AnchorPane> </AnchorPane>
<StackPane GridPane.columnIndex="0" GridPane.rowIndex="2" GridPane.columnSpan="3">
<TransactionDiagram fx:id="transactionDiagram" />
</StackPane>
</GridPane> </GridPane>
</center> </center>
<bottom> <bottom>
@ -96,9 +100,8 @@
</padding> </padding>
<HBox AnchorPane.rightAnchor="10"> <HBox AnchorPane.rightAnchor="10">
<Button fx:id="clear" text="Clear" cancelButton="true" onAction="#clear" /> <Button fx:id="clear" text="Clear" cancelButton="true" onAction="#clear" />
<Region HBox.hgrow="ALWAYS" /> <Region HBox.hgrow="ALWAYS" style="-fx-min-width: 20px" />
<Button fx:id="select" text="Select UTXOs" defaultButton="true" onAction="#selectUtxos" /> <Button fx:id="create" text="Create Transaction" defaultButton="true" disable="true" onAction="#createTransaction" />
<Button fx:id="create" text="Create Transaction" defaultButton="true" onAction="#createTransaction" />
</HBox> </HBox>
</AnchorPane> </AnchorPane>
</bottom> </bottom>