diff --git a/drongo b/drongo index 3ce23948..4b4a980a 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit 3ce2394813749be99e79f4f0253f2636fab9df91 +Subproject commit 4b4a980a9bead1589a1db4202ac5ed386832f4ab diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index 9e680ef2..b8aebf05 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -165,7 +165,11 @@ public class AppController implements Initializable { tabs.setTabDragPolicy(TabPane.TabDragPolicy.REORDER); tabs.getTabs().addListener((ListChangeListener) c -> { if(c.next() && (c.wasAdded() || c.wasRemoved())) { - EventManager.get().post(new OpenWalletsEvent(getOpenWallets())); + boolean walletAdded = c.getAddedSubList().stream().anyMatch(tab -> ((TabData)tab.getUserData()).getType() == TabData.TabType.WALLET); + boolean walletRemoved = c.getRemoved().stream().anyMatch(tab -> ((TabData)tab.getUserData()).getType() == TabData.TabType.WALLET); + if(walletAdded || walletRemoved) { + EventManager.get().post(new OpenWalletsEvent(getOpenWallets())); + } } }); @@ -721,9 +725,10 @@ public class AppController implements Initializable { //If an exact match bytewise of an existing tab, return that tab if(Arrays.equals(transactionTabData.getTransaction().bitcoinSerialize(), transaction.bitcoinSerialize())) { - //As per BIP174, combine PSBTs with matching transactions - if(transactionTabData.getPsbt() != null && psbt != null) { + //As per BIP174, combine PSBTs with matching transactions so long as they are not yet finalized + if(transactionTabData.getPsbt() != null && psbt != null && !transactionTabData.getPsbt().isFinalized() && !psbt.isFinalized()) { transactionTabData.getPsbt().combine(psbt); + tab.setText(name); EventManager.get().post(new PSBTCombinedEvent(transactionTabData.getPsbt())); } diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java index 575dd207..aeb919f3 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java @@ -4,10 +4,7 @@ import com.sparrowwallet.drongo.SecureString; import com.sparrowwallet.drongo.protocol.*; import com.sparrowwallet.drongo.psbt.PSBT; import com.sparrowwallet.drongo.psbt.PSBTInput; -import com.sparrowwallet.drongo.wallet.BlockTransaction; -import com.sparrowwallet.drongo.wallet.Keystore; -import com.sparrowwallet.drongo.wallet.KeystoreSource; -import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.drongo.wallet.*; import com.sparrowwallet.sparrow.AppController; import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.control.*; @@ -45,6 +42,7 @@ import java.util.stream.Collectors; public class HeadersController extends TransactionFormController implements Initializable { public static final String LOCKTIME_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; public static final String BLOCK_TIMESTAMP_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss ZZZ"; + public static final String UNFINALIZED_TXID_CLASS = "unfinalized-txid"; private HeadersForm headersForm; @@ -298,9 +296,7 @@ public class HeadersController extends TransactionFormController implements Init signingWalletForm.managedProperty().bind(signingWalletForm.visibleProperty()); sigHashForm.managedProperty().bind(sigHashForm.visibleProperty()); - sigHashForm.visibleProperty().bind(signingWalletForm.visibleProperty()); finalizeButtonBox.managedProperty().bind(finalizeButtonBox.visibleProperty()); - finalizeButtonBox.visibleProperty().bind(signingWalletForm.visibleProperty()); signaturesForm.managedProperty().bind(signaturesForm.visibleProperty()); signButtonBox.managedProperty().bind(signButtonBox.visibleProperty()); @@ -308,6 +304,8 @@ public class HeadersController extends TransactionFormController implements Init blockchainForm.setVisible(false); signingWalletForm.setVisible(false); + sigHashForm.setVisible(false); + finalizeButtonBox.setVisible(false); signaturesForm.setVisible(false); signButtonBox.setVisible(false); broadcastButtonBox.setVisible(false); @@ -320,16 +318,17 @@ public class HeadersController extends TransactionFormController implements Init if(headersForm.isEditable()) { signingWalletForm.setVisible(true); + sigHashForm.setVisible(true); + finalizeButtonBox.setVisible(true); } else if(headersForm.getPsbt().isSigned()) { signaturesForm.setVisible(true); broadcastButtonBox.setVisible(true); } else { - signaturesForm.setVisible(true); - signButtonBox.setVisible(true); + signingWalletForm.setVisible(true); + finalizeButtonBox.setVisible(true); + finalizeTransaction.setText("Set Signing Wallet"); } - EventManager.get().post(new RequestOpenWalletsEvent()); - signingWallet.managedProperty().bind(signingWallet.visibleProperty()); noWalletsWarning.managedProperty().bind(noWalletsWarning.visibleProperty()); noWalletsWarningLink.managedProperty().bind(noWalletsWarningLink.visibleProperty()); @@ -355,6 +354,8 @@ public class HeadersController extends TransactionFormController implements Init int threshold = signingWallet.getDefaultPolicy().getNumSignaturesRequired(); signaturesProgressBar.initialize(headersForm.getSignedKeystores(), threshold); }); + + EventManager.get().post(new RequestOpenWalletsEvent()); } } @@ -460,8 +461,12 @@ public class HeadersController extends TransactionFormController implements Init private void updateTxId() { id.setText(headersForm.getTransaction().calculateTxId(false).toString()); - if(headersForm.getPsbt() != null && headersForm.isEditable()) { - id.getStyleClass().add("unfinalized-psbt"); + if(headersForm.getPsbt() != null && !(headersForm.getTransaction().hasScriptSigs() || headersForm.getTransaction().hasWitnesses())) { + if(!id.getStyleClass().contains(UNFINALIZED_TXID_CLASS)) { + id.getStyleClass().add(UNFINALIZED_TXID_CLASS); + } + } else { + id.getStyleClass().remove(UNFINALIZED_TXID_CLASS); } } @@ -591,7 +596,17 @@ public class HeadersController extends TransactionFormController implements Init }); } + private void finalizePSBT() { + if(headersForm.getPsbt() != null && headersForm.getPsbt().isSigned() && !headersForm.getPsbt().isFinalized()) { + headersForm.getSigningWallet().finalise(headersForm.getPsbt()); + EventManager.get().post(new PSBTFinalizedEvent(headersForm.getPsbt())); + } + } + public void extractTransaction(ActionEvent event) { + Button viewFinalButton = (Button)event.getSource(); + viewFinalButton.setDisable(true); + Transaction finalTx = headersForm.getPsbt().extractTransaction(); headersForm.setFinalTransaction(finalTx); EventManager.get().post(new TransactionExtractedEvent(headersForm.getPsbt(), finalTx)); @@ -637,27 +652,55 @@ public class HeadersController extends TransactionFormController implements Init @Subscribe public void openWallets(OpenWalletsEvent event) { - if(headersForm.getPsbt() != null && headersForm.isEditable()) { + if(headersForm.getPsbt() != null) { List availableWallets = event.getWallets().stream().filter(wallet -> wallet.canSign(headersForm.getPsbt())).collect(Collectors.toList()); Map availableWalletsMap = new LinkedHashMap<>(event.getWalletsMap()); availableWalletsMap.keySet().retainAll(availableWallets); headersForm.getAvailableWallets().keySet().retainAll(availableWallets); headersForm.getAvailableWallets().putAll(availableWalletsMap); - signingWallet.setItems(FXCollections.observableList(availableWallets)); + if(!availableWallets.isEmpty()) { - if(availableWallets.contains(headersForm.getSigningWallet())) { - signingWallet.setValue(headersForm.getSigningWallet()); + if(!headersForm.isEditable() && (availableWallets.size() == 1 || headersForm.getPsbt().isSigned())) { + signingWalletForm.setVisible(false); + sigHashForm.setVisible(false); + finalizeButtonBox.setVisible(false); + + signaturesForm.setVisible(true); + headersForm.setSigningWallet(availableWallets.get(0)); + + if(headersForm.getPsbt().isSigned()) { + finalizePSBT(); + broadcastButtonBox.setVisible(true); + } else { + signButtonBox.setVisible(true); + } } else { - signingWallet.setValue(availableWallets.get(0)); + if(availableWallets.contains(headersForm.getSigningWallet())) { + signingWallet.setValue(headersForm.getSigningWallet()); + } else { + signingWallet.setValue(availableWallets.get(0)); + } + noWalletsWarning.setVisible(false); + signingWallet.setVisible(true); + finalizeTransaction.setDisable(false); } - noWalletsWarning.setVisible(false); - signingWallet.setVisible(true); - finalizeTransaction.setDisable(false); } else { - noWalletsWarning.setVisible(true); - signingWallet.setVisible(false); - finalizeTransaction.setDisable(true); + if(headersForm.getPsbt().isSigned()) { + if(headersForm.getSigningWallet() == null) { + //As no signing wallet is available, but we want to show the PSBT has been signed and automatically finalize it, construct a special wallet with default named keystores + Wallet signedWallet = new FinalizingPSBTWallet(headersForm.getPsbt()); + headersForm.setSigningWallet(signedWallet); + } + + //Finalize this PSBT if necessary as fully signed PSBTs are automatically finalized on once the signature threshold has been reached + finalizePSBT(); + broadcastButtonBox.setVisible(true); + } else { + noWalletsWarning.setVisible(true); + signingWallet.setVisible(false); + finalizeTransaction.setDisable(true); + } } } } @@ -671,20 +714,34 @@ public class HeadersController extends TransactionFormController implements Init locktimeDateType.setDisable(true); locktimeBlock.setDisable(true); locktimeDate.setDisable(true); - id.getStyleClass().remove("unfinalized-psbt"); + updateTxId(); headersForm.setSigningWallet(event.getSigningWallet()); signingWalletForm.setVisible(false); + sigHashForm.setVisible(false); + finalizeButtonBox.setVisible(false); signaturesForm.setVisible(true); - signButtonBox.setVisible(true); + + if(event.getPsbt().isSigned()) { + broadcastButtonBox.setVisible(true); + } else { + signButtonBox.setVisible(true); + } } } @Subscribe public void psbtCombined(PSBTCombinedEvent event) { - if(event.getPsbt().equals(headersForm.getPsbt()) && headersForm.getSigningWallet() != null) { - updateSignedKeystores(headersForm.getSigningWallet()); + if(event.getPsbt().equals(headersForm.getPsbt())) { + if(headersForm.getSigningWallet() != null) { + updateSignedKeystores(headersForm.getSigningWallet()); + } else if(headersForm.getPsbt().isSigned()) { + Wallet signedWallet = new FinalizingPSBTWallet(headersForm.getPsbt()); + headersForm.setSigningWallet(signedWallet); + finalizePSBT(); + EventManager.get().post(new FinalizeTransactionEvent(headersForm.getPsbt(), signedWallet)); + } } } @@ -699,10 +756,14 @@ public class HeadersController extends TransactionFormController implements Init @Subscribe public void keystoreSigned(KeystoreSignedEvent event) { if(headersForm.getSignedKeystores().contains(event.getKeystore()) && headersForm.getPsbt() != null) { - if(headersForm.getPsbt().isSigned()) { - headersForm.getSigningWallet().finalise(headersForm.getPsbt()); - EventManager.get().post(new PSBTFinalizedEvent(headersForm.getPsbt())); - } + finalizePSBT(); + } + } + + @Subscribe + public void transactionExtracted(TransactionExtractedEvent event) { + if(event.getPsbt().equals(headersForm.getPsbt())) { + updateTxId(); } } } \ No newline at end of file diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/InputController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/InputController.java index d1f4035b..5c370588 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/InputController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/InputController.java @@ -227,6 +227,11 @@ public class InputController extends TransactionFormController implements Initia } private void updateScriptFields(TransactionInput txInput, PSBTInput psbtInput) { + //Don't use PSBT data if txInput has scriptSig or witness data. This happens when a tx has been extracted from a PSBT + if(txInput.getScriptBytes().length > 0 || txInput.hasWitness()) { + psbtInput = null; + } + scriptSigArea.clear(); redeemScriptArea.clear(); witnessesArea.clear(); @@ -258,6 +263,7 @@ public class InputController extends TransactionFormController implements Initia } if(redeemScript != null) { + redeemScriptArea.setDisable(false); redeemScriptArea.appendScript(redeemScript); } else { redeemScriptScroll.setDisable(true); @@ -282,12 +288,14 @@ public class InputController extends TransactionFormController implements Initia } if(witnesses != null) { + witnessesScroll.setDisable(false); witnessesArea.appendScript(witnesses, null, witnessScript); } else { witnessesScroll.setDisable(true); } if(witnessScript != null) { + witnessScriptScroll.setDisable(false); witnessScriptArea.appendScript(witnessScript); } else { witnessScriptScroll.setDisable(true); @@ -335,13 +343,7 @@ public class InputController extends TransactionFormController implements Initia } } - int foundSigs = psbtInput.getPartialSignatures().size(); - if(psbtInput.getFinalScriptWitness() != null) { - foundSigs = psbtInput.getFinalScriptWitness().getSignatures().size(); - } else if(psbtInput.getFinalScriptSig() != null) { - foundSigs = psbtInput.getFinalScriptSig().getSignatures().size(); - } - + int foundSigs = psbtInput.getSignatures().size(); signatures.setText(foundSigs + "/" + (reqSigs < 0 ? "?" : reqSigs)); } } diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/InputForm.java b/src/main/java/com/sparrowwallet/sparrow/transaction/InputForm.java index 2844edf0..d065c59c 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/InputForm.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/InputForm.java @@ -11,34 +11,27 @@ import javafx.scene.Node; import java.io.IOException; public class InputForm extends IndexedTransactionForm { - private final TransactionInput transactionInput; - private final PSBTInput psbtInput; - public InputForm(TransactionData txdata, PSBTInput psbtInput) { super(txdata, txdata.getPsbt().getPsbtInputs().indexOf(psbtInput)); - this.transactionInput = txdata.getPsbt().getTransaction().getInputs().get(txdata.getPsbt().getPsbtInputs().indexOf(psbtInput)); - this.psbtInput = psbtInput; } public InputForm(TransactionData txdata, TransactionInput transactionInput) { super(txdata, txdata.getTransaction().getInputs().indexOf(transactionInput)); - this.transactionInput = transactionInput; - this.psbtInput = null; } public TransactionInput getTransactionInput() { - return transactionInput; + return txdata.getTransaction().getInputs().get(getIndex()); } public PSBTInput getPsbtInput() { - return psbtInput; + return txdata.getPsbt().getPsbtInputs().get(getIndex()); } public TransactionOutput getReferencedTransactionOutput() { if(getInputTransactions() != null) { - BlockTransaction inputTransaction = getInputTransactions().get(transactionInput.getOutpoint().getHash()); + BlockTransaction inputTransaction = getInputTransactions().get(getTransactionInput().getOutpoint().getHash()); if(inputTransaction != null && !inputTransaction.equals(ElectrumServer.UNFETCHABLE_BLOCK_TRANSACTION)) { - return inputTransaction.getTransaction().getOutputs().get((int)transactionInput.getOutpoint().getIndex()); + return inputTransaction.getTransaction().getOutputs().get((int)getTransactionInput().getOutpoint().getIndex()); } } @@ -61,6 +54,6 @@ public class InputForm extends IndexedTransactionForm { } public String toString() { - return "Input #" + transactionInput.getIndex(); + return "Input #" + getIndex(); } } diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/InputsController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/InputsController.java index 871b8e7c..aee9fd49 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/InputsController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/InputsController.java @@ -88,13 +88,7 @@ public class InputsController extends TransactionFormController implements Initi showDenominator = false; } - if(psbtInput.getFinalScriptWitness() != null) { - foundSigs += psbtInput.getFinalScriptWitness().getSignatures().size(); - } else if(psbtInput.getFinalScriptSig() != null) { - foundSigs += psbtInput.getFinalScriptSig().getSignatures().size(); - } else { - foundSigs += psbtInput.getPartialSignatures().size(); - } + foundSigs += psbtInput.getSignatures().size(); } long totalAmt = 0; diff --git a/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.css b/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.css index 922f1f7c..a36a004a 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.css +++ b/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.css @@ -20,11 +20,11 @@ .locktime { -fx-fill: #986801 } -.unfinalized-psbt { +.unfinalized-txid { -fx-text-fill: #a0a1a7; } -#finalizeForm .input-container { +#signingWalletForm .input-container { -fx-alignment: center-left; -fx-pref-height: 30; } diff --git a/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.fxml b/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.fxml index af938a10..1b310962 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.fxml @@ -133,12 +133,12 @@
-