diff --git a/drongo b/drongo index 2650dafa..79eb8b00 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit 2650dafa66623c1205582c555369a5118a343ccf +Subproject commit 79eb8b002d01be5195bb7fc7eba6bb34bf3366e3 diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index de20be71..3262b102 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -137,6 +137,8 @@ public class AppController implements Initializable { private static Map targetBlockFeeRates; + private static Double minimumRelayFeeRate; + private static CurrencyRate fiatCurrencyExchangeRate; private static List devices; @@ -643,6 +645,10 @@ public class AppController implements Initializable { return targetBlockFeeRates; } + public static Double getMinimumRelayFeeRate() { + return minimumRelayFeeRate; + } + public static CurrencyRate getFiatCurrencyExchangeRate() { return fiatCurrencyExchangeRate; } @@ -1305,6 +1311,7 @@ public class AppController implements Initializable { public void newConnection(ConnectionEvent event) { currentBlockHeight = event.getBlockHeight(); targetBlockFeeRates = event.getTargetBlockFeeRates(); + minimumRelayFeeRate = event.getMinimumRelayFeeRate(); String banner = event.getServerBanner(); String status = "Connected to " + Config.get().getElectrumServer() + " at height " + event.getBlockHeight(); EventManager.get().post(new StatusEvent(status)); diff --git a/src/main/java/com/sparrowwallet/sparrow/event/ConnectionEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/ConnectionEvent.java index 0ca3665b..4deaed37 100644 --- a/src/main/java/com/sparrowwallet/sparrow/event/ConnectionEvent.java +++ b/src/main/java/com/sparrowwallet/sparrow/event/ConnectionEvent.java @@ -10,13 +10,15 @@ public class ConnectionEvent extends FeeRatesUpdatedEvent { private final String serverBanner; private final int blockHeight; private final BlockHeader blockHeader; + private final Double minimumRelayFeeRate; - public ConnectionEvent(List serverVersion, String serverBanner, int blockHeight, BlockHeader blockHeader, Map targetBlockFeeRates) { + public ConnectionEvent(List serverVersion, String serverBanner, int blockHeight, BlockHeader blockHeader, Map targetBlockFeeRates, Double minimumRelayFeeRate) { super(targetBlockFeeRates); this.serverVersion = serverVersion; this.serverBanner = serverBanner; this.blockHeight = blockHeight; this.blockHeader = blockHeader; + this.minimumRelayFeeRate = minimumRelayFeeRate; } public List getServerVersion() { @@ -34,4 +36,8 @@ public class ConnectionEvent extends FeeRatesUpdatedEvent { public BlockHeader getBlockHeader() { return blockHeader; } + + public Double getMinimumRelayFeeRate() { + return minimumRelayFeeRate; + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/net/BatchedElectrumServerRpc.java b/src/main/java/com/sparrowwallet/sparrow/net/BatchedElectrumServerRpc.java index b415db64..1188ca3a 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/BatchedElectrumServerRpc.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/BatchedElectrumServerRpc.java @@ -205,6 +205,16 @@ public class BatchedElectrumServerRpc implements ElectrumServerRpc { } } + @Override + public Double getMinimumRelayFee(Transport transport) { + try { + JsonRpcClient client = new JsonRpcClient(transport); + return client.createRequest().returnAs(Double.class).method("blockchain.relayfee").id(1).execute(); + } catch(JsonRpcException e) { + throw new ElectrumServerRpcException("Error getting minimum relay fee", e); + } + } + @Override public String broadcastTransaction(Transport transport, String txHex) { try { diff --git a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java index 8e1e4038..ee9918df 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java @@ -528,7 +528,7 @@ public class ElectrumServer { Map targetBlocksFeeRatesSats = new TreeMap<>(); for(Integer target : targetBlocksFeeRatesBtcKb.keySet()) { - targetBlocksFeeRatesSats.put(target, targetBlocksFeeRatesBtcKb.get(target) * Transaction.SATOSHIS_PER_BITCOIN / 1024); + targetBlocksFeeRatesSats.put(target, targetBlocksFeeRatesBtcKb.get(target) * Transaction.SATOSHIS_PER_BITCOIN / 1000); } return targetBlocksFeeRatesSats; @@ -537,6 +537,16 @@ public class ElectrumServer { } } + public Double getMinimumRelayFee() throws ServerException { + Double minFeeRateBtcKb = electrumServerRpc.getMinimumRelayFee(getTransport()); + if(minFeeRateBtcKb != null) { + long minFeeRateSatsKb = (long)(minFeeRateBtcKb * Transaction.SATOSHIS_PER_BITCOIN); + return minFeeRateSatsKb / 1000d; + } + + return Transaction.DEFAULT_MIN_RELAY_FEE; + } + public Sha256Hash broadcastTransaction(Transaction transaction) throws ServerException { byte[] rawtxBytes = transaction.bitcoinSerialize(); String rawtxHex = Utils.bytesToHex(rawtxBytes); @@ -667,7 +677,12 @@ public class ElectrumServer { Map blockTargetFeeRates = electrumServer.getFeeEstimates(SendController.TARGET_BLOCKS_RANGE); feeRatesRetrievedAt = System.currentTimeMillis(); - return new ConnectionEvent(serverVersion, banner, tip.height, tip.getBlockHeader(), blockTargetFeeRates); + Double minimumRelayFeeRate = electrumServer.getMinimumRelayFee(); + for(Integer blockTarget : blockTargetFeeRates.keySet()) { + blockTargetFeeRates.computeIfPresent(blockTarget, (blocks, feeRate) -> feeRate < minimumRelayFeeRate ? minimumRelayFeeRate : feeRate); + } + + return new ConnectionEvent(serverVersion, banner, tip.height, tip.getBlockHeader(), blockTargetFeeRates, minimumRelayFeeRate); } else { if(reader.isAlive()) { electrumServer.ping(); diff --git a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServerRpc.java b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServerRpc.java index fdf37ea7..62744f11 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServerRpc.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServerRpc.java @@ -29,5 +29,7 @@ public interface ElectrumServerRpc { Map getFeeEstimates(Transport transport, List targetBlocks); + Double getMinimumRelayFee(Transport transport); + String broadcastTransaction(Transport transport, String txHex); } diff --git a/src/main/java/com/sparrowwallet/sparrow/net/SimpleElectrumServerRpc.java b/src/main/java/com/sparrowwallet/sparrow/net/SimpleElectrumServerRpc.java index 2d6a26ef..92aae1a4 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/SimpleElectrumServerRpc.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/SimpleElectrumServerRpc.java @@ -229,6 +229,16 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc { return result; } + @Override + public Double getMinimumRelayFee(Transport transport) { + try { + JsonRpcClient client = new JsonRpcClient(transport); + return client.createRequest().returnAs(Double.class).method("blockchain.relayfee").id(1).execute(); + } catch(JsonRpcException e) { + throw new ElectrumServerRpcException("Error getting minimum relay fee", e); + } + } + @Override public String broadcastTransaction(Transport transport, String txHex) { try { diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java index be9df799..c1b11d0d 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java @@ -306,7 +306,9 @@ public class SendController extends WalletFormController implements Initializabl )); validationSupport.registerValidator(fee, Validator.combine( (Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Insufficient Inputs", userFeeSet.get() && insufficientInputsProperty.get()), - (Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Insufficient Fee", getFeeValueSats() != null && getFeeValueSats() == 0) + (Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Insufficient Fee", getFeeValueSats() != null && getFeeValueSats() == 0), + (Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Insufficient Fee Rate", walletTransactionProperty.get() != null && + (double)walletTransactionProperty.get().getFee() / walletTransactionProperty.get().getTransaction().getVirtualSize() < AppController.getMinimumRelayFeeRate()) )); validationSupport.setValidationDecorator(new StyleClassValidationDecoration()); @@ -445,7 +447,7 @@ public class SendController extends WalletFormController implements Initializabl LinkedHashMap::new)); } - return retrievedFeeRates; + return retrievedFeeRates; } private Double getFeeRate() {