From c814b8e3505f2b0814598218204d53e13c9dff02 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Fri, 21 Aug 2020 17:16:14 +0200 Subject: [PATCH] various fixes, better non-electrumx support --- build.gradle | 2 +- drongo | 2 +- .../ConfirmationProgressIndicator.java | 2 +- .../control/MnemonicKeystoreImportPane.java | 2 +- .../event/WalletNodeHistoryChangedEvent.java | 4 ++ .../sparrow/net/BatchedElectrumServerRpc.java | 2 +- .../sparrow/net/ElectrumServer.java | 12 +++-- .../sparrow/net/ElectrumServerRpc.java | 2 +- .../sparrow/net/SimpleElectrumServerRpc.java | 48 +++++++++++++++---- .../sparrow/net/TcpTransport.java | 21 +++++++- .../transaction/HeadersController.java | 11 +++-- .../transaction/TransactionController.java | 8 ++-- .../sparrowwallet/sparrow/wallet/receive.fxml | 4 +- .../com/sparrowwallet/sparrow/wallet/send.css | 4 +- .../sparrowwallet/sparrow/wallet/send.fxml | 2 +- 15 files changed, 93 insertions(+), 33 deletions(-) diff --git a/build.gradle b/build.gradle index 85ee4d7f..64fc57be 100644 --- a/build.gradle +++ b/build.gradle @@ -118,7 +118,7 @@ jlink { jpackage { imageName = "Sparrow" installerName = "Sparrow" - appVersion = "0.6" + appVersion = "0.9.1" skipInstaller = true imageOptions = [] installerOptions = [ diff --git a/drongo b/drongo index 5d456a10..446eac34 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit 5d456a10df1375b41e6159efc540b3d90cfbfc4d +Subproject commit 446eac3483229fc4c798e82f367ca9d8f797d16e diff --git a/src/main/java/com/sparrowwallet/sparrow/control/ConfirmationProgressIndicator.java b/src/main/java/com/sparrowwallet/sparrow/control/ConfirmationProgressIndicator.java index f405772a..f0d4647a 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/ConfirmationProgressIndicator.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/ConfirmationProgressIndicator.java @@ -54,7 +54,7 @@ public class ConfirmationProgressIndicator extends StackPane { arcLengthTimeline.getKeyFrames().add(arcLengthFrame); sequence.getChildren().add(arcLengthTimeline); - if(newValue.intValue() == BlockTransactionHash.BLOCKS_TO_CONFIRM) { + if(newValue.intValue() >= BlockTransactionHash.BLOCKS_TO_CONFIRM) { Timeline arcRadiusTimeline = new Timeline(); KeyValue arcRadiusXValue = new KeyValue(arc.radiusXProperty(), 0.0); KeyValue arcRadiusYValue = new KeyValue(arc.radiusYProperty(), 0.0); diff --git a/src/main/java/com/sparrowwallet/sparrow/control/MnemonicKeystoreImportPane.java b/src/main/java/com/sparrowwallet/sparrow/control/MnemonicKeystoreImportPane.java index a743f28c..026bf2eb 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/MnemonicKeystoreImportPane.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/MnemonicKeystoreImportPane.java @@ -63,7 +63,7 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane { } public MnemonicKeystoreImportPane(Keystore keystore) { - super(keystore.getSeed().getType().getName(), keystore.getSeed().needsPassphrase() ? "Passphrase enabled" : "Passphrase disabled", "", "image/" + WalletModel.SEED + ".png"); + super(keystore.getSeed().getType().getName(), keystore.getSeed().needsPassphrase() ? "Passphrase enabled" : "Passphrase disabled", "", "image/" + WalletModel.SEED.getType() + ".png"); this.wallet = null; this.importer = null; showHideLink.setVisible(false); diff --git a/src/main/java/com/sparrowwallet/sparrow/event/WalletNodeHistoryChangedEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/WalletNodeHistoryChangedEvent.java index 0f5aa09a..152fb912 100644 --- a/src/main/java/com/sparrowwallet/sparrow/event/WalletNodeHistoryChangedEvent.java +++ b/src/main/java/com/sparrowwallet/sparrow/event/WalletNodeHistoryChangedEvent.java @@ -40,4 +40,8 @@ public class WalletNodeHistoryChangedEvent { return null; } + + public String getScriptHash() { + return scriptHash; + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/net/BatchedElectrumServerRpc.java b/src/main/java/com/sparrowwallet/sparrow/net/BatchedElectrumServerRpc.java index a0a641e5..dd6f9a23 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/BatchedElectrumServerRpc.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/BatchedElectrumServerRpc.java @@ -167,7 +167,7 @@ public class BatchedElectrumServerRpc implements ElectrumServerRpc { @Override @SuppressWarnings("unchecked") - public Map getVerboseTransactions(Transport transport, Set txids) { + public Map getVerboseTransactions(Transport transport, Set txids, String scriptHash) { JsonRpcClient client = new JsonRpcClient(transport); BatchRequestBuilder batchRequest = client.createBatchRequest().keysType(String.class).returnType(VerboseTransaction.class); for(String txid : txids) { diff --git a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java index 52bc535a..94de6a27 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java @@ -479,13 +479,13 @@ public class ElectrumServer { } } - public Map getReferencedTransactions(Set references) throws ServerException { + public Map getReferencedTransactions(Set references, String scriptHash) throws ServerException { Set txids = new LinkedHashSet<>(references.size()); for(Sha256Hash reference : references) { txids.add(reference.toString()); } - Map result = electrumServerRpc.getVerboseTransactions(getTransport(), txids); + Map result = electrumServerRpc.getVerboseTransactions(getTransport(), txids, scriptHash); Map transactionMap = new HashMap<>(); for(String txid : result.keySet()) { @@ -715,6 +715,7 @@ public class ElectrumServer { public static class TransactionReferenceService extends Service> { private final Set references; + private String scriptHash; public TransactionReferenceService(Transaction transaction) { references = new HashSet<>(); @@ -724,6 +725,11 @@ public class ElectrumServer { } } + public TransactionReferenceService(Set references, String scriptHash) { + this(references); + this.scriptHash = scriptHash; + } + public TransactionReferenceService(Set references) { this.references = references; } @@ -733,7 +739,7 @@ public class ElectrumServer { return new Task<>() { protected Map call() throws ServerException { ElectrumServer electrumServer = new ElectrumServer(); - return electrumServer.getReferencedTransactions(references); + return electrumServer.getReferencedTransactions(references, scriptHash); } }; } diff --git a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServerRpc.java b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServerRpc.java index 1aea0d77..fdf37ea7 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServerRpc.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServerRpc.java @@ -25,7 +25,7 @@ public interface ElectrumServerRpc { Map getTransactions(Transport transport, Set txids); - Map getVerboseTransactions(Transport transport, Set txids); + Map getVerboseTransactions(Transport transport, Set txids, String scriptHash); Map getFeeEstimates(Transport transport, List targetBlocks); diff --git a/src/main/java/com/sparrowwallet/sparrow/net/SimpleElectrumServerRpc.java b/src/main/java/com/sparrowwallet/sparrow/net/SimpleElectrumServerRpc.java index 591ce8e0..491be7bd 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/SimpleElectrumServerRpc.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/SimpleElectrumServerRpc.java @@ -1,10 +1,12 @@ package com.sparrowwallet.sparrow.net; -import com.fasterxml.jackson.databind.exc.MismatchedInputException; import com.github.arteam.simplejsonrpc.client.JsonRpcClient; import com.github.arteam.simplejsonrpc.client.Transport; import com.github.arteam.simplejsonrpc.client.exception.JsonRpcException; +import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.protocol.Sha256Hash; +import com.sparrowwallet.drongo.protocol.Transaction; +import com.sparrowwallet.sparrow.AppController; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,6 +19,7 @@ import static com.sparrowwallet.drongo.protocol.Transaction.DUST_RELAY_TX_FEE; public class SimpleElectrumServerRpc implements ElectrumServerRpc { private static final Logger log = LoggerFactory.getLogger(SimpleElectrumServerRpc.class); + private static final int MAX_TARGET_BLOCKS = 25; @Override public void ping(Transport transport) { @@ -156,7 +159,7 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc { } @Override - public Map getVerboseTransactions(Transport transport, Set txids) { + public Map getVerboseTransactions(Transport transport, Set txids, String scriptHash) { JsonRpcClient client = new JsonRpcClient(transport); Map result = new LinkedHashMap<>(); @@ -166,6 +169,27 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc { result.put(txid, verboseTransaction); } catch(IllegalStateException | IllegalArgumentException e) { log.warn("Error retrieving transaction: " + txid + " (" + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()) + ")"); + String rawTxHex = client.createRequest().returnAs(String.class).method("blockchain.transaction.get").id(txid).params(txid).execute(); + Transaction tx = new Transaction(Utils.hexToBytes(rawTxHex)); + String id = tx.getTxId().toString(); + int height = 0; + + if(scriptHash != null) { + ScriptHashTx[] scriptHashTxes = client.createRequest().returnAs(ScriptHashTx[].class).method("blockchain.scripthash.get_history").id(id).params(scriptHash).execute(); + for(ScriptHashTx scriptHashTx : scriptHashTxes) { + if(scriptHashTx.tx_hash.equals(id)) { + height = scriptHashTx.height; + break; + } + } + } + + VerboseTransaction verboseTransaction = new VerboseTransaction(); + verboseTransaction.txid = id; + verboseTransaction.hex = rawTxHex; + verboseTransaction.confirmations = (height <= 0 ? 0 : AppController.getCurrentBlockHeight() - height + 1); + verboseTransaction.blockhash = Sha256Hash.ZERO_HASH.toString(); + result.put(txid, verboseTransaction); } catch(JsonRpcException e) { log.warn("Error retrieving transaction: " + txid + " (" + e.getErrorMessage() + ")"); } @@ -180,14 +204,18 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc { Map result = new LinkedHashMap<>(); for(Integer targetBlock : targetBlocks) { - try { - Double targetBlocksFeeRateBtcKb = client.createRequest().returnAs(Double.class).method("blockchain.estimatefee").id(targetBlock).params(targetBlock).execute(); - result.put(targetBlock, targetBlocksFeeRateBtcKb); - } catch(IllegalStateException | IllegalArgumentException e) { - log.warn("Failed to retrieve fee rate for target blocks: " + targetBlock + " (" + e.getMessage() + ")"); - result.put(targetBlock, DUST_RELAY_TX_FEE); - } catch(JsonRpcException e) { - throw new ElectrumServerRpcException("Failed to retrieve fee rate for target blocks: " + targetBlock, e); + if(targetBlock <= MAX_TARGET_BLOCKS) { + try { + Double targetBlocksFeeRateBtcKb = client.createRequest().returnAs(Double.class).method("blockchain.estimatefee").id(targetBlock).params(targetBlock).execute(); + result.put(targetBlock, targetBlocksFeeRateBtcKb); + } catch(IllegalStateException | IllegalArgumentException e) { + log.warn("Failed to retrieve fee rate for target blocks: " + targetBlock + " (" + e.getMessage() + ")"); + result.put(targetBlock, 1d); + } catch(JsonRpcException e) { + throw new ElectrumServerRpcException("Failed to retrieve fee rate for target blocks: " + targetBlock, e); + } + } else { + result.put(targetBlock, 1d); } } diff --git a/src/main/java/com/sparrowwallet/sparrow/net/TcpTransport.java b/src/main/java/com/sparrowwallet/sparrow/net/TcpTransport.java index 4bb700fc..e43faa20 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/TcpTransport.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/TcpTransport.java @@ -5,6 +5,8 @@ import com.github.arteam.simplejsonrpc.server.JsonRpcServer; import com.google.common.net.HostAndPort; import com.sparrowwallet.sparrow.io.Config; import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.net.SocketFactory; import java.io.*; @@ -12,6 +14,8 @@ import java.net.Socket; import java.util.concurrent.locks.ReentrantLock; public class TcpTransport implements Transport, Closeable { + private static final Logger log = LoggerFactory.getLogger(TcpTransport.class); + public static final int DEFAULT_PORT = 50001; protected final HostAndPort server; @@ -24,6 +28,7 @@ public class TcpTransport implements Transport, Closeable { private final ReentrantLock clientRequestLock = new ReentrantLock(); private boolean running = false; private boolean reading = true; + private boolean firstRead = true; private final JsonRpcServer jsonRpcServer = new JsonRpcServer(); private final SubscriptionService subscriptionService = new SubscriptionService(); @@ -53,6 +58,11 @@ public class TcpTransport implements Transport, Closeable { } private synchronized String readResponse() throws IOException { + if(firstRead) { + notifyAll(); + firstRead = false; + } + while(reading) { try { wait(); @@ -63,7 +73,7 @@ public class TcpTransport implements Transport, Closeable { } if(lastException != null) { - throw new IOException("Error reading response", lastException); + throw new IOException("Error reading response: " + lastException.getMessage(), lastException); } reading = true; @@ -73,6 +83,13 @@ public class TcpTransport implements Transport, Closeable { } public synchronized void readInputLoop() throws ServerException { + try { + //Don't start reading until first RPC request is sent + wait(); + } catch(InterruptedException e) { + Thread.currentThread().interrupt(); + } + while(running) { try { String received = readInputStream(); @@ -95,7 +112,7 @@ public class TcpTransport implements Transport, Closeable { reading = false; notifyAll(); //Allow this thread to terminate as we will need to reconnect with a new transport anyway - throw new ServerException(e); + running = false; } } } diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java index 4335aa44..4d5c2f26 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java @@ -469,7 +469,10 @@ public class HeadersController extends TransactionFormController implements Init private void updateBlockchainForm(BlockTransaction blockTransaction, Integer currentHeight) { blockchainForm.setVisible(true); - if(currentHeight == null) { + if(Sha256Hash.ZERO_HASH.equals(blockTransaction.getBlockHash()) && blockTransaction.getHeight() == 0 && headersForm.getSigningWallet() == null) { + //A zero block hash indicates that this blocktransaction is incomplete and the height is likely incorrect if we are not sending a tx + blockStatus.setText("Unknown"); + } else if(currentHeight == null) { blockStatus.setText(blockTransaction.getHeight() > 0 ? "Confirmed" : "Unconfirmed"); } else { int confirmations = blockTransaction.getHeight() > 0 ? currentHeight - blockTransaction.getHeight() + 1 : 0; @@ -512,7 +515,7 @@ public class HeadersController extends TransactionFormController implements Init blockTimestampField.setVisible(false); } - if(blockTransaction.getBlockHash() != null) { + if(blockTransaction.getBlockHash() != null && !blockTransaction.getBlockHash().equals(Sha256Hash.ZERO_HASH)) { blockHashField.setVisible(true); blockHash.setText(blockTransaction.getBlockHash().toString()); blockHash.setContextMenu(new BlockHeightContextMenu(blockTransaction.getBlockHash())); @@ -796,7 +799,7 @@ public class HeadersController extends TransactionFormController implements Init @Subscribe public void blockTransactionFetched(BlockTransactionFetchedEvent event) { if(event.getTxId().equals(headersForm.getTransaction().getTxId())) { - if(event.getBlockTransaction() != null) { + if(event.getBlockTransaction() != null && (!Sha256Hash.ZERO_HASH.equals(event.getBlockTransaction().getBlockHash()) || headersForm.getBlockTransaction() == null)) { updateBlockchainForm(event.getBlockTransaction(), AppController.getCurrentBlockHeight()); } @@ -937,7 +940,7 @@ public class HeadersController extends TransactionFormController implements Init public void walletNodeHistoryChanged(WalletNodeHistoryChangedEvent event) { if(headersForm.getSigningWallet() != null && event.getWalletNode(headersForm.getSigningWallet()) != null) { Sha256Hash txid = headersForm.getTransaction().getTxId(); - ElectrumServer.TransactionReferenceService transactionReferenceService = new ElectrumServer.TransactionReferenceService(Set.of(txid)); + ElectrumServer.TransactionReferenceService transactionReferenceService = new ElectrumServer.TransactionReferenceService(Set.of(txid), event.getScriptHash()); transactionReferenceService.setOnSucceeded(successEvent -> { Map transactionMap = transactionReferenceService.getValue(); BlockTransaction blockTransaction = transactionMap.get(txid); diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionController.java index 396cb6a4..d0e91a31 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/TransactionController.java @@ -319,9 +319,9 @@ public class TransactionController implements Initializable { Map transactionMap = transactionReferenceService.getValue(); BlockTransaction thisBlockTx = null; Map inputTransactions = new HashMap<>(); - for (Sha256Hash txid : transactionMap.keySet()) { + for(Sha256Hash txid : transactionMap.keySet()) { BlockTransaction blockTx = transactionMap.get(txid); - if (txid.equals(getTransaction().getTxId())) { + if(txid.equals(getTransaction().getTxId())) { thisBlockTx = blockTx; } else { inputTransactions.put(txid, blockTx); @@ -456,7 +456,9 @@ public class TransactionController implements Initializable { @Subscribe public void blockTransactionFetched(BlockTransactionFetchedEvent event) { if(event.getTxId().equals(getTransaction().getTxId())) { - txdata.setBlockTransaction(event.getBlockTransaction()); + if(event.getBlockTransaction() != null && (!Sha256Hash.ZERO_HASH.equals(event.getBlockTransaction().getBlockHash()) || txdata.getBlockTransaction() == null)) { + txdata.setBlockTransaction(event.getBlockTransaction()); + } if(txdata.getInputTransactions() == null) { txdata.setInputTransactions(event.getInputTransactions()); } else { diff --git a/src/main/resources/com/sparrowwallet/sparrow/wallet/receive.fxml b/src/main/resources/com/sparrowwallet/sparrow/wallet/receive.fxml index 000c427f..48db8c71 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/wallet/receive.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/wallet/receive.fxml @@ -56,8 +56,8 @@
-
- +
+ diff --git a/src/main/resources/com/sparrowwallet/sparrow/wallet/send.css b/src/main/resources/com/sparrowwallet/sparrow/wallet/send.css index da5d44b9..1212c0e7 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/wallet/send.css +++ b/src/main/resources/com/sparrowwallet/sparrow/wallet/send.css @@ -13,8 +13,8 @@ } .amount-unit { - -fx-min-width: 70px; - -fx-max-width: 70px; + -fx-min-width: 75px; + -fx-max-width: 75px; } #feeRatesChart { diff --git a/src/main/resources/com/sparrowwallet/sparrow/wallet/send.fxml b/src/main/resources/com/sparrowwallet/sparrow/wallet/send.fxml index 195619ee..7ed93471 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/wallet/send.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/wallet/send.fxml @@ -73,7 +73,7 @@
- +