diff --git a/build.gradle b/build.gradle index 65faa187..d57c77ea 100644 --- a/build.gradle +++ b/build.gradle @@ -48,7 +48,7 @@ tasks.withType(AbstractArchiveTask) { } javafx { - version = headless ? "18" : "22" + version = headless ? "18" : "23.0.1" modules = [ 'javafx.controls', 'javafx.fxml', 'javafx.swing', 'javafx.graphics' ] } diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index d311994d..9da23ce9 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -274,7 +274,7 @@ public class AppController implements Initializable { } void initializeView() { - setPlatformApplicationMenu(); + Platform.runLater(this::setPlatformApplicationMenu); rootStack.getScene().getWindow().setOnHiding(windowEvent -> { if(searchWalletDialog != null && searchWalletDialog.isShowing()) { @@ -450,7 +450,7 @@ public class AppController implements Initializable { OsType osType = OsType.getCurrent(); if(osType == OsType.MACOS) { MenuToolkit tk = MenuToolkit.toolkit(); - MenuItem preferences = new MenuItem("Preferences..."); + MenuItem preferences = new MenuItem("Settings..."); preferences.setOnAction(this::openPreferences); preferences.setAccelerator(new KeyCodeCombination(KeyCode.COMMA, KeyCombination.META_DOWN)); Menu defaultApplicationMenu = new Menu("Apple", null, tk.createAboutMenuItem(SparrowWallet.APP_NAME, getAboutStage()), new SeparatorMenuItem(), diff --git a/src/main/java/com/sparrowwallet/sparrow/net/ConfigurationException.java b/src/main/java/com/sparrowwallet/sparrow/net/ConfigurationException.java new file mode 100644 index 00000000..9057125d --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/net/ConfigurationException.java @@ -0,0 +1,11 @@ +package com.sparrowwallet.sparrow.net; + +public class ConfigurationException extends RuntimeException { + public ConfigurationException(String message) { + super(message); + } + + public ConfigurationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/BitcoindClient.java b/src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/BitcoindClient.java index 73458e22..caede21d 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/BitcoindClient.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/BitcoindClient.java @@ -17,6 +17,7 @@ import com.sparrowwallet.sparrow.event.CormorantScanStatusEvent; import com.sparrowwallet.sparrow.event.CormorantSyncStatusEvent; import com.sparrowwallet.sparrow.io.Config; import com.sparrowwallet.sparrow.net.Bwt; +import com.sparrowwallet.sparrow.net.ConfigurationException; import com.sparrowwallet.sparrow.net.CoreAuthType; import com.sparrowwallet.sparrow.net.cormorant.Cormorant; import com.sparrowwallet.drongo.address.Address; @@ -94,8 +95,10 @@ public class BitcoindClient { Config config = Config.get(); if((config.getCoreAuthType() == CoreAuthType.COOKIE || config.getCoreAuth() == null || config.getCoreAuth().length() < 2) && config.getCoreDataDir() != null) { bitcoindTransport = new BitcoindTransport(config.getCoreServer(), CORE_WALLET_NAME, config.getCoreDataDir()); - } else { + } else if(config.getCoreAuth() != null) { bitcoindTransport = new BitcoindTransport(config.getCoreServer(), CORE_WALLET_NAME, config.getCoreAuth()); + } else { + throw new ConfigurationException("Bitcoin Core data folder or user and password is required"); } this.jsonRpcClient = new JsonRpcClient(bitcoindTransport); @@ -114,7 +117,7 @@ public class BitcoindClient { tip = blockHeader.getBlockHeader(); timer.schedule(new PollTask(), 5000, 5000); - if(blockchainInfo.initialblockdownload()) { + if(blockchainInfo.initialblockdownload() && networkInfo.networkactive()) { syncingLock.lock(); try { syncing = true; diff --git a/src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/MempoolEntry.java b/src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/MempoolEntry.java index 27f42836..a62980ab 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/MempoolEntry.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/MempoolEntry.java @@ -10,7 +10,7 @@ public record MempoolEntry(int vsize, int ancestorsize, boolean bip125_replaceab } public TxEntry getTxEntry(String txid) { - return new TxEntry(hasUnconfirmedParents() ? -1 : 0, 0, txid); + return new TxEntry(hasUnconfirmedParents() ? -1 : 0, 0, txid, fees().base()); } public VsizeFeerate getVsizeFeerate() { diff --git a/src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/NetworkInfo.java b/src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/NetworkInfo.java index 131a28e7..67e198a5 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/NetworkInfo.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/cormorant/bitcoind/NetworkInfo.java @@ -3,6 +3,6 @@ package com.sparrowwallet.sparrow.net.cormorant.bitcoind; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @JsonIgnoreProperties(ignoreUnknown = true) -public record NetworkInfo(int version, String subversion) { +public record NetworkInfo(int version, String subversion, boolean networkactive) { } diff --git a/src/main/java/com/sparrowwallet/sparrow/net/cormorant/electrum/ElectrumServerService.java b/src/main/java/com/sparrowwallet/sparrow/net/cormorant/electrum/ElectrumServerService.java index 3eaf3b7a..d9659dc1 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/cormorant/electrum/ElectrumServerService.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/cormorant/electrum/ElectrumServerService.java @@ -45,7 +45,7 @@ public class ElectrumServerService { @JsonRpcMethod("server.banner") public String getServerBanner() { - return Cormorant.SERVER_NAME + " " + SparrowWallet.APP_VERSION + "\n" + bitcoindClient.getNetworkInfo().subversion(); + return Cormorant.SERVER_NAME + " " + SparrowWallet.APP_VERSION + "\n" + bitcoindClient.getNetworkInfo().subversion() + (bitcoindClient.getNetworkInfo().networkactive() ? "" : " (disconnected)"); } @JsonRpcMethod("blockchain.estimatefee") diff --git a/src/main/java/com/sparrowwallet/sparrow/net/cormorant/index/Store.java b/src/main/java/com/sparrowwallet/sparrow/net/cormorant/index/Store.java index 3f87d0b5..765a003a 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/cormorant/index/Store.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/cormorant/index/Store.java @@ -35,7 +35,7 @@ public class Store { mempoolEntries.put(txid, null); } entries.removeIf(txe -> txe.height > 0 && txe.tx_hash.equals(listTransaction.txid())); - txEntry = new TxEntry(0, 0, listTransaction.txid()); + txEntry = new TxEntry(0, 0, listTransaction.txid(), listTransaction.fee()); } else { mempoolEntries.remove(txid); entries.removeIf(txe -> txe.height != listTransaction.blockheight() && txe.tx_hash.equals(listTransaction.txid())); diff --git a/src/main/java/com/sparrowwallet/sparrow/net/cormorant/index/TxEntry.java b/src/main/java/com/sparrowwallet/sparrow/net/cormorant/index/TxEntry.java index 9e16b512..bd5d6a87 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/cormorant/index/TxEntry.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/cormorant/index/TxEntry.java @@ -1,14 +1,29 @@ package com.sparrowwallet.sparrow.net.cormorant.index; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.sparrowwallet.drongo.protocol.Transaction; + +import java.util.Objects; + +@JsonInclude(JsonInclude.Include.NON_NULL) public class TxEntry implements Comparable { public final int height; private final transient int index; public final String tx_hash; + public final Long fee; public TxEntry(int height, int index, String tx_hash) { this.height = height; this.index = index; this.tx_hash = tx_hash; + this.fee = null; + } + + public TxEntry(int height, int index, String tx_hash, double btcFee) { + this.height = height; + this.index = index; + this.tx_hash = tx_hash; + this.fee = btcFee > 0.0 ? (long)(btcFee * Transaction.SATOSHIS_PER_BITCOIN) : null; } @Override @@ -16,22 +31,18 @@ public class TxEntry implements Comparable { if(this == o) { return true; } - if(o == null || getClass() != o.getClass()) { + if(!(o instanceof TxEntry txEntry)) { return false; } - TxEntry txEntry = (TxEntry) o; - - if(height != txEntry.height) { - return false; - } - return tx_hash.equals(txEntry.tx_hash); + return height == txEntry.height && tx_hash.equals(txEntry.tx_hash) && Objects.equals(fee, txEntry.fee); } @Override public int hashCode() { int result = height; result = 31 * result + tx_hash.hashCode(); + result = 31 * result + Objects.hashCode(fee); return result; } @@ -62,6 +73,7 @@ public class TxEntry implements Comparable { "height=" + height + ", index=" + index + ", tx_hash='" + tx_hash + '\'' + + ", fee=" + fee + '}'; } } diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java index b727e7f9..3cb46668 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java @@ -877,18 +877,23 @@ public class SendController extends WalletFormController implements Initializabl walletTransaction.getSelectedUtxos().keySet().stream().filter(ref -> ref.getHeight() <= 0) .map(ref -> getWalletForm().getWallet().getWalletTransaction(ref.getHash())) .filter(Objects::nonNull).distinct().collect(Collectors.toList()); - if(!unconfirmedUtxoTxs.isEmpty()) { + if(!unconfirmedUtxoTxs.isEmpty() && unconfirmedUtxoTxs.stream().allMatch(blkTx -> blkTx.getFee() != null && blkTx.getFee() > 0)) { long utxoTxFee = unconfirmedUtxoTxs.stream().mapToLong(BlockTransaction::getFee).sum(); double utxoTxSize = unconfirmedUtxoTxs.stream().mapToDouble(blkTx -> blkTx.getTransaction().getVirtualSize()).sum(); long thisFee = walletTransaction.getFee(); double thisSize = walletTransaction.getTransaction().getVirtualSize(); + double thisRate = thisFee / thisSize; double effectiveRate = (utxoTxFee + thisFee) / (utxoTxSize + thisSize); - UnitFormat format = Config.get().getUnitFormat() == null ? UnitFormat.DOT : Config.get().getUnitFormat(); - String strEffectiveRate = format.getCurrencyFormat().format(effectiveRate); - Tooltip tooltip = new Tooltip("CPFP (Child Pays For Parent)\n" + strEffectiveRate + " sats/vB effective rate"); - cpfpFeeRate.setTooltip(tooltip); - cpfpFeeRate.setVisible(true); - cpfpFeeRate.setText(strEffectiveRate + " sats/vB (CPFP)"); + if(thisRate > effectiveRate) { + UnitFormat format = Config.get().getUnitFormat() == null ? UnitFormat.DOT : Config.get().getUnitFormat(); + String strEffectiveRate = format.getCurrencyFormat().format(effectiveRate); + Tooltip tooltip = new Tooltip("CPFP (Child Pays For Parent)\n" + strEffectiveRate + " sats/vB effective rate"); + cpfpFeeRate.setTooltip(tooltip); + cpfpFeeRate.setVisible(true); + cpfpFeeRate.setText(strEffectiveRate + " sats/vB (CPFP)"); + } else { + cpfpFeeRate.setVisible(false); + } } else { cpfpFeeRate.setVisible(false); }