combining and finalising psbt fixes

This commit is contained in:
Craig Raw 2020-07-30 10:00:09 +02:00
parent 29cfda7908
commit 75f5fd2e12
8 changed files with 120 additions and 65 deletions

2
drongo

@ -1 +1 @@
Subproject commit 3ce2394813749be99e79f4f0253f2636fab9df91 Subproject commit 4b4a980a9bead1589a1db4202ac5ed386832f4ab

View file

@ -165,8 +165,12 @@ public class AppController implements Initializable {
tabs.setTabDragPolicy(TabPane.TabDragPolicy.REORDER); tabs.setTabDragPolicy(TabPane.TabDragPolicy.REORDER);
tabs.getTabs().addListener((ListChangeListener<Tab>) c -> { tabs.getTabs().addListener((ListChangeListener<Tab>) c -> {
if(c.next() && (c.wasAdded() || c.wasRemoved())) { if(c.next() && (c.wasAdded() || c.wasRemoved())) {
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())); EventManager.get().post(new OpenWalletsEvent(getOpenWallets()));
} }
}
}); });
BitcoinUnit unit = Config.get().getBitcoinUnit(); BitcoinUnit unit = Config.get().getBitcoinUnit();
@ -721,9 +725,10 @@ public class AppController implements Initializable {
//If an exact match bytewise of an existing tab, return that tab //If an exact match bytewise of an existing tab, return that tab
if(Arrays.equals(transactionTabData.getTransaction().bitcoinSerialize(), transaction.bitcoinSerialize())) { if(Arrays.equals(transactionTabData.getTransaction().bitcoinSerialize(), transaction.bitcoinSerialize())) {
//As per BIP174, combine PSBTs with matching transactions //As per BIP174, combine PSBTs with matching transactions so long as they are not yet finalized
if(transactionTabData.getPsbt() != null && psbt != null) { if(transactionTabData.getPsbt() != null && psbt != null && !transactionTabData.getPsbt().isFinalized() && !psbt.isFinalized()) {
transactionTabData.getPsbt().combine(psbt); transactionTabData.getPsbt().combine(psbt);
tab.setText(name);
EventManager.get().post(new PSBTCombinedEvent(transactionTabData.getPsbt())); EventManager.get().post(new PSBTCombinedEvent(transactionTabData.getPsbt()));
} }

View file

@ -4,10 +4,7 @@ import com.sparrowwallet.drongo.SecureString;
import com.sparrowwallet.drongo.protocol.*; import com.sparrowwallet.drongo.protocol.*;
import com.sparrowwallet.drongo.psbt.PSBT; import com.sparrowwallet.drongo.psbt.PSBT;
import com.sparrowwallet.drongo.psbt.PSBTInput; import com.sparrowwallet.drongo.psbt.PSBTInput;
import com.sparrowwallet.drongo.wallet.BlockTransaction; import com.sparrowwallet.drongo.wallet.*;
import com.sparrowwallet.drongo.wallet.Keystore;
import com.sparrowwallet.drongo.wallet.KeystoreSource;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.AppController; import com.sparrowwallet.sparrow.AppController;
import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.control.*; import com.sparrowwallet.sparrow.control.*;
@ -45,6 +42,7 @@ import java.util.stream.Collectors;
public class HeadersController extends TransactionFormController implements Initializable { public class HeadersController extends TransactionFormController implements Initializable {
public static final String LOCKTIME_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; 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 BLOCK_TIMESTAMP_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss ZZZ";
public static final String UNFINALIZED_TXID_CLASS = "unfinalized-txid";
private HeadersForm headersForm; private HeadersForm headersForm;
@ -298,9 +296,7 @@ public class HeadersController extends TransactionFormController implements Init
signingWalletForm.managedProperty().bind(signingWalletForm.visibleProperty()); signingWalletForm.managedProperty().bind(signingWalletForm.visibleProperty());
sigHashForm.managedProperty().bind(sigHashForm.visibleProperty()); sigHashForm.managedProperty().bind(sigHashForm.visibleProperty());
sigHashForm.visibleProperty().bind(signingWalletForm.visibleProperty());
finalizeButtonBox.managedProperty().bind(finalizeButtonBox.visibleProperty()); finalizeButtonBox.managedProperty().bind(finalizeButtonBox.visibleProperty());
finalizeButtonBox.visibleProperty().bind(signingWalletForm.visibleProperty());
signaturesForm.managedProperty().bind(signaturesForm.visibleProperty()); signaturesForm.managedProperty().bind(signaturesForm.visibleProperty());
signButtonBox.managedProperty().bind(signButtonBox.visibleProperty()); signButtonBox.managedProperty().bind(signButtonBox.visibleProperty());
@ -308,6 +304,8 @@ public class HeadersController extends TransactionFormController implements Init
blockchainForm.setVisible(false); blockchainForm.setVisible(false);
signingWalletForm.setVisible(false); signingWalletForm.setVisible(false);
sigHashForm.setVisible(false);
finalizeButtonBox.setVisible(false);
signaturesForm.setVisible(false); signaturesForm.setVisible(false);
signButtonBox.setVisible(false); signButtonBox.setVisible(false);
broadcastButtonBox.setVisible(false); broadcastButtonBox.setVisible(false);
@ -320,16 +318,17 @@ public class HeadersController extends TransactionFormController implements Init
if(headersForm.isEditable()) { if(headersForm.isEditable()) {
signingWalletForm.setVisible(true); signingWalletForm.setVisible(true);
sigHashForm.setVisible(true);
finalizeButtonBox.setVisible(true);
} else if(headersForm.getPsbt().isSigned()) { } else if(headersForm.getPsbt().isSigned()) {
signaturesForm.setVisible(true); signaturesForm.setVisible(true);
broadcastButtonBox.setVisible(true); broadcastButtonBox.setVisible(true);
} else { } else {
signaturesForm.setVisible(true); signingWalletForm.setVisible(true);
signButtonBox.setVisible(true); finalizeButtonBox.setVisible(true);
finalizeTransaction.setText("Set Signing Wallet");
} }
EventManager.get().post(new RequestOpenWalletsEvent());
signingWallet.managedProperty().bind(signingWallet.visibleProperty()); signingWallet.managedProperty().bind(signingWallet.visibleProperty());
noWalletsWarning.managedProperty().bind(noWalletsWarning.visibleProperty()); noWalletsWarning.managedProperty().bind(noWalletsWarning.visibleProperty());
noWalletsWarningLink.managedProperty().bind(noWalletsWarningLink.visibleProperty()); noWalletsWarningLink.managedProperty().bind(noWalletsWarningLink.visibleProperty());
@ -355,6 +354,8 @@ public class HeadersController extends TransactionFormController implements Init
int threshold = signingWallet.getDefaultPolicy().getNumSignaturesRequired(); int threshold = signingWallet.getDefaultPolicy().getNumSignaturesRequired();
signaturesProgressBar.initialize(headersForm.getSignedKeystores(), threshold); signaturesProgressBar.initialize(headersForm.getSignedKeystores(), threshold);
}); });
EventManager.get().post(new RequestOpenWalletsEvent());
} }
} }
@ -460,8 +461,12 @@ public class HeadersController extends TransactionFormController implements Init
private void updateTxId() { private void updateTxId() {
id.setText(headersForm.getTransaction().calculateTxId(false).toString()); id.setText(headersForm.getTransaction().calculateTxId(false).toString());
if(headersForm.getPsbt() != null && headersForm.isEditable()) { if(headersForm.getPsbt() != null && !(headersForm.getTransaction().hasScriptSigs() || headersForm.getTransaction().hasWitnesses())) {
id.getStyleClass().add("unfinalized-psbt"); 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) { public void extractTransaction(ActionEvent event) {
Button viewFinalButton = (Button)event.getSource();
viewFinalButton.setDisable(true);
Transaction finalTx = headersForm.getPsbt().extractTransaction(); Transaction finalTx = headersForm.getPsbt().extractTransaction();
headersForm.setFinalTransaction(finalTx); headersForm.setFinalTransaction(finalTx);
EventManager.get().post(new TransactionExtractedEvent(headersForm.getPsbt(), finalTx)); EventManager.get().post(new TransactionExtractedEvent(headersForm.getPsbt(), finalTx));
@ -637,15 +652,30 @@ public class HeadersController extends TransactionFormController implements Init
@Subscribe @Subscribe
public void openWallets(OpenWalletsEvent event) { public void openWallets(OpenWalletsEvent event) {
if(headersForm.getPsbt() != null && headersForm.isEditable()) { if(headersForm.getPsbt() != null) {
List<Wallet> availableWallets = event.getWallets().stream().filter(wallet -> wallet.canSign(headersForm.getPsbt())).collect(Collectors.toList()); List<Wallet> availableWallets = event.getWallets().stream().filter(wallet -> wallet.canSign(headersForm.getPsbt())).collect(Collectors.toList());
Map<Wallet, Storage> availableWalletsMap = new LinkedHashMap<>(event.getWalletsMap()); Map<Wallet, Storage> availableWalletsMap = new LinkedHashMap<>(event.getWalletsMap());
availableWalletsMap.keySet().retainAll(availableWallets); availableWalletsMap.keySet().retainAll(availableWallets);
headersForm.getAvailableWallets().keySet().retainAll(availableWallets); headersForm.getAvailableWallets().keySet().retainAll(availableWallets);
headersForm.getAvailableWallets().putAll(availableWalletsMap); headersForm.getAvailableWallets().putAll(availableWalletsMap);
signingWallet.setItems(FXCollections.observableList(availableWallets)); signingWallet.setItems(FXCollections.observableList(availableWallets));
if(!availableWallets.isEmpty()) { if(!availableWallets.isEmpty()) {
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 {
if(availableWallets.contains(headersForm.getSigningWallet())) { if(availableWallets.contains(headersForm.getSigningWallet())) {
signingWallet.setValue(headersForm.getSigningWallet()); signingWallet.setValue(headersForm.getSigningWallet());
} else { } else {
@ -654,6 +684,18 @@ public class HeadersController extends TransactionFormController implements Init
noWalletsWarning.setVisible(false); noWalletsWarning.setVisible(false);
signingWallet.setVisible(true); signingWallet.setVisible(true);
finalizeTransaction.setDisable(false); finalizeTransaction.setDisable(false);
}
} else {
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 { } else {
noWalletsWarning.setVisible(true); noWalletsWarning.setVisible(true);
signingWallet.setVisible(false); signingWallet.setVisible(false);
@ -661,6 +703,7 @@ public class HeadersController extends TransactionFormController implements Init
} }
} }
} }
}
@Subscribe @Subscribe
public void finalizeTransaction(FinalizeTransactionEvent event) { public void finalizeTransaction(FinalizeTransactionEvent event) {
@ -671,20 +714,34 @@ public class HeadersController extends TransactionFormController implements Init
locktimeDateType.setDisable(true); locktimeDateType.setDisable(true);
locktimeBlock.setDisable(true); locktimeBlock.setDisable(true);
locktimeDate.setDisable(true); locktimeDate.setDisable(true);
id.getStyleClass().remove("unfinalized-psbt"); updateTxId();
headersForm.setSigningWallet(event.getSigningWallet()); headersForm.setSigningWallet(event.getSigningWallet());
signingWalletForm.setVisible(false); signingWalletForm.setVisible(false);
sigHashForm.setVisible(false);
finalizeButtonBox.setVisible(false);
signaturesForm.setVisible(true); signaturesForm.setVisible(true);
if(event.getPsbt().isSigned()) {
broadcastButtonBox.setVisible(true);
} else {
signButtonBox.setVisible(true); signButtonBox.setVisible(true);
} }
} }
}
@Subscribe @Subscribe
public void psbtCombined(PSBTCombinedEvent event) { public void psbtCombined(PSBTCombinedEvent event) {
if(event.getPsbt().equals(headersForm.getPsbt()) && headersForm.getSigningWallet() != null) { if(event.getPsbt().equals(headersForm.getPsbt())) {
if(headersForm.getSigningWallet() != null) {
updateSignedKeystores(headersForm.getSigningWallet()); 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 @Subscribe
public void keystoreSigned(KeystoreSignedEvent event) { public void keystoreSigned(KeystoreSignedEvent event) {
if(headersForm.getSignedKeystores().contains(event.getKeystore()) && headersForm.getPsbt() != null) { if(headersForm.getSignedKeystores().contains(event.getKeystore()) && headersForm.getPsbt() != null) {
if(headersForm.getPsbt().isSigned()) { finalizePSBT();
headersForm.getSigningWallet().finalise(headersForm.getPsbt());
EventManager.get().post(new PSBTFinalizedEvent(headersForm.getPsbt()));
} }
} }
@Subscribe
public void transactionExtracted(TransactionExtractedEvent event) {
if(event.getPsbt().equals(headersForm.getPsbt())) {
updateTxId();
}
} }
} }

View file

@ -227,6 +227,11 @@ public class InputController extends TransactionFormController implements Initia
} }
private void updateScriptFields(TransactionInput txInput, PSBTInput psbtInput) { 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(); scriptSigArea.clear();
redeemScriptArea.clear(); redeemScriptArea.clear();
witnessesArea.clear(); witnessesArea.clear();
@ -258,6 +263,7 @@ public class InputController extends TransactionFormController implements Initia
} }
if(redeemScript != null) { if(redeemScript != null) {
redeemScriptArea.setDisable(false);
redeemScriptArea.appendScript(redeemScript); redeemScriptArea.appendScript(redeemScript);
} else { } else {
redeemScriptScroll.setDisable(true); redeemScriptScroll.setDisable(true);
@ -282,12 +288,14 @@ public class InputController extends TransactionFormController implements Initia
} }
if(witnesses != null) { if(witnesses != null) {
witnessesScroll.setDisable(false);
witnessesArea.appendScript(witnesses, null, witnessScript); witnessesArea.appendScript(witnesses, null, witnessScript);
} else { } else {
witnessesScroll.setDisable(true); witnessesScroll.setDisable(true);
} }
if(witnessScript != null) { if(witnessScript != null) {
witnessScriptScroll.setDisable(false);
witnessScriptArea.appendScript(witnessScript); witnessScriptArea.appendScript(witnessScript);
} else { } else {
witnessScriptScroll.setDisable(true); witnessScriptScroll.setDisable(true);
@ -335,13 +343,7 @@ public class InputController extends TransactionFormController implements Initia
} }
} }
int foundSigs = psbtInput.getPartialSignatures().size(); int foundSigs = psbtInput.getSignatures().size();
if(psbtInput.getFinalScriptWitness() != null) {
foundSigs = psbtInput.getFinalScriptWitness().getSignatures().size();
} else if(psbtInput.getFinalScriptSig() != null) {
foundSigs = psbtInput.getFinalScriptSig().getSignatures().size();
}
signatures.setText(foundSigs + "/" + (reqSigs < 0 ? "?" : reqSigs)); signatures.setText(foundSigs + "/" + (reqSigs < 0 ? "?" : reqSigs));
} }
} }

View file

@ -11,34 +11,27 @@ import javafx.scene.Node;
import java.io.IOException; import java.io.IOException;
public class InputForm extends IndexedTransactionForm { public class InputForm extends IndexedTransactionForm {
private final TransactionInput transactionInput;
private final PSBTInput psbtInput;
public InputForm(TransactionData txdata, PSBTInput psbtInput) { public InputForm(TransactionData txdata, PSBTInput psbtInput) {
super(txdata, txdata.getPsbt().getPsbtInputs().indexOf(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) { public InputForm(TransactionData txdata, TransactionInput transactionInput) {
super(txdata, txdata.getTransaction().getInputs().indexOf(transactionInput)); super(txdata, txdata.getTransaction().getInputs().indexOf(transactionInput));
this.transactionInput = transactionInput;
this.psbtInput = null;
} }
public TransactionInput getTransactionInput() { public TransactionInput getTransactionInput() {
return transactionInput; return txdata.getTransaction().getInputs().get(getIndex());
} }
public PSBTInput getPsbtInput() { public PSBTInput getPsbtInput() {
return psbtInput; return txdata.getPsbt().getPsbtInputs().get(getIndex());
} }
public TransactionOutput getReferencedTransactionOutput() { public TransactionOutput getReferencedTransactionOutput() {
if(getInputTransactions() != null) { 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)) { 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() { public String toString() {
return "Input #" + transactionInput.getIndex(); return "Input #" + getIndex();
} }
} }

View file

@ -88,13 +88,7 @@ public class InputsController extends TransactionFormController implements Initi
showDenominator = false; showDenominator = false;
} }
if(psbtInput.getFinalScriptWitness() != null) { foundSigs += psbtInput.getSignatures().size();
foundSigs += psbtInput.getFinalScriptWitness().getSignatures().size();
} else if(psbtInput.getFinalScriptSig() != null) {
foundSigs += psbtInput.getFinalScriptSig().getSignatures().size();
} else {
foundSigs += psbtInput.getPartialSignatures().size();
}
} }
long totalAmt = 0; long totalAmt = 0;

View file

@ -20,11 +20,11 @@
.locktime { -fx-fill: #986801 } .locktime { -fx-fill: #986801 }
.unfinalized-psbt { .unfinalized-txid {
-fx-text-fill: #a0a1a7; -fx-text-fill: #a0a1a7;
} }
#finalizeForm .input-container { #signingWalletForm .input-container {
-fx-alignment: center-left; -fx-alignment: center-left;
-fx-pref-height: 30; -fx-pref-height: 30;
} }

View file

@ -133,12 +133,12 @@
<Fieldset text="Signatures" inputGrow="SOMETIMES"> <Fieldset text="Signatures" inputGrow="SOMETIMES">
<Field text="Signing Wallet:"> <Field text="Signing Wallet:">
<ComboBox fx:id="signingWallet" /> <ComboBox fx:id="signingWallet" />
<Label fx:id="noWalletsWarning" graphicTextGap="5" text="No open wallets can sign this PSBT. "> <Label fx:id="noWalletsWarning" graphicTextGap="5" text="No open wallets can sign. ">
<graphic> <graphic>
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="12" icon="EXCLAMATION_CIRCLE" /> <Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="12" icon="EXCLAMATION_CIRCLE" />
</graphic> </graphic>
</Label> </Label>
<Hyperlink fx:id="noWalletsWarningLink" text="Open another wallet?" onAction="#openWallet" /> <Hyperlink fx:id="noWalletsWarningLink" text="Open wallet?" onAction="#openWallet" />
</Field> </Field>
</Fieldset> </Fieldset>
</Form> </Form>