mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2025-11-05 11:56:37 +00:00
retrieve and show next block median fee rate in recent blocks view where available
This commit is contained in:
parent
52470ee6d8
commit
231eb13cee
7 changed files with 95 additions and 10 deletions
|
|
@ -136,6 +136,8 @@ public class AppServices {
|
||||||
|
|
||||||
private static Map<Integer, Double> targetBlockFeeRates;
|
private static Map<Integer, Double> targetBlockFeeRates;
|
||||||
|
|
||||||
|
private static Double nextBlockMedianFeeRate;
|
||||||
|
|
||||||
private static final TreeMap<Date, Set<MempoolRateSize>> mempoolHistogram = new TreeMap<>();
|
private static final TreeMap<Date, Set<MempoolRateSize>> mempoolHistogram = new TreeMap<>();
|
||||||
|
|
||||||
private static Double minimumRelayFeeRate;
|
private static Double minimumRelayFeeRate;
|
||||||
|
|
@ -748,6 +750,10 @@ public class AppServices {
|
||||||
return Math.max(minRate, Transaction.DUST_RELAY_TX_FEE);
|
return Math.max(minRate, Transaction.DUST_RELAY_TX_FEE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Double getNextBlockMedianFeeRate() {
|
||||||
|
return nextBlockMedianFeeRate == null ? getDefaultFeeRate() : nextBlockMedianFeeRate;
|
||||||
|
}
|
||||||
|
|
||||||
public static double getFallbackFeeRate() {
|
public static double getFallbackFeeRate() {
|
||||||
return Network.get() == Network.MAINNET ? FALLBACK_FEE_RATE : TESTNET_FALLBACK_FEE_RATE;
|
return Network.get() == Network.MAINNET ? FALLBACK_FEE_RATE : TESTNET_FALLBACK_FEE_RATE;
|
||||||
}
|
}
|
||||||
|
|
@ -1249,11 +1255,13 @@ public class AppServices {
|
||||||
if(AppServices.currentBlockHeight != null) {
|
if(AppServices.currentBlockHeight != null) {
|
||||||
blockSummaries.keySet().removeIf(height -> AppServices.currentBlockHeight - height > 5);
|
blockSummaries.keySet().removeIf(height -> AppServices.currentBlockHeight - height > 5);
|
||||||
}
|
}
|
||||||
|
nextBlockMedianFeeRate = event.getNextBlockMedianFeeRate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void feesUpdated(FeeRatesUpdatedEvent event) {
|
public void feesUpdated(FeeRatesUpdatedEvent event) {
|
||||||
targetBlockFeeRates = event.getTargetBlockFeeRates();
|
targetBlockFeeRates = event.getTargetBlockFeeRates();
|
||||||
|
nextBlockMedianFeeRate = event.getNextBlockMedianFeeRate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ public class RecentBlocksView extends Pane {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateFeeRatesSource(FeeRatesSource feeRatesSource) {
|
public void updateFeeRatesSource(FeeRatesSource feeRatesSource) {
|
||||||
tooltip.setText("Fee rate estimate from " + feeRatesSource.getDescription());
|
tooltip.setText("Fee rates from " + feeRatesSource.getDescription());
|
||||||
if(getCubes() != null && !getCubes().isEmpty()) {
|
if(getCubes() != null && !getCubes().isEmpty()) {
|
||||||
getCubes().getFirst().setFeeRatesSource(feeRatesSource);
|
getCubes().getFirst().setFeeRatesSource(feeRatesSource);
|
||||||
}
|
}
|
||||||
|
|
@ -108,7 +108,7 @@ public class RecentBlocksView extends Pane {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addNewBlock(List<BlockSummary> latestBlocks, Double currentFeeRate) {
|
private void addNewBlock(List<BlockSummary> latestBlocks, Double currentFeeRate) {
|
||||||
if(getCubes().isEmpty()) {
|
if(getCubes().isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,18 @@ import java.util.Map;
|
||||||
|
|
||||||
public class BlockSummaryEvent {
|
public class BlockSummaryEvent {
|
||||||
private final Map<Integer, BlockSummary> blockSummaryMap;
|
private final Map<Integer, BlockSummary> blockSummaryMap;
|
||||||
|
private final Double nextBlockMedianFeeRate;
|
||||||
|
|
||||||
public BlockSummaryEvent(Map<Integer, BlockSummary> blockSummaryMap) {
|
public BlockSummaryEvent(Map<Integer, BlockSummary> blockSummaryMap, Double nextBlockMedianFeeRate) {
|
||||||
this.blockSummaryMap = blockSummaryMap;
|
this.blockSummaryMap = blockSummaryMap;
|
||||||
|
this.nextBlockMedianFeeRate = nextBlockMedianFeeRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<Integer, BlockSummary> getBlockSummaryMap() {
|
public Map<Integer, BlockSummary> getBlockSummaryMap() {
|
||||||
return blockSummaryMap;
|
return blockSummaryMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Double getNextBlockMedianFeeRate() {
|
||||||
|
return nextBlockMedianFeeRate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,23 @@ import java.util.Set;
|
||||||
|
|
||||||
public class FeeRatesUpdatedEvent extends MempoolRateSizesUpdatedEvent {
|
public class FeeRatesUpdatedEvent extends MempoolRateSizesUpdatedEvent {
|
||||||
private final Map<Integer, Double> targetBlockFeeRates;
|
private final Map<Integer, Double> targetBlockFeeRates;
|
||||||
|
private final Double nextBlockMedianFeeRate;
|
||||||
|
|
||||||
public FeeRatesUpdatedEvent(Map<Integer, Double> targetBlockFeeRates, Set<MempoolRateSize> mempoolRateSizes) {
|
public FeeRatesUpdatedEvent(Map<Integer, Double> targetBlockFeeRates, Set<MempoolRateSize> mempoolRateSizes) {
|
||||||
|
this(targetBlockFeeRates, mempoolRateSizes, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FeeRatesUpdatedEvent(Map<Integer, Double> targetBlockFeeRates, Set<MempoolRateSize> mempoolRateSizes, Double nextBlockMedianFeeRate) {
|
||||||
super(mempoolRateSizes);
|
super(mempoolRateSizes);
|
||||||
this.targetBlockFeeRates = targetBlockFeeRates;
|
this.targetBlockFeeRates = targetBlockFeeRates;
|
||||||
|
this.nextBlockMedianFeeRate = nextBlockMedianFeeRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<Integer, Double> getTargetBlockFeeRates() {
|
public Map<Integer, Double> getTargetBlockFeeRates() {
|
||||||
return targetBlockFeeRates;
|
return targetBlockFeeRates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Double getNextBlockMedianFeeRate() {
|
||||||
|
return nextBlockMedianFeeRate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -936,6 +936,20 @@ public class ElectrumServer {
|
||||||
return targetBlocksFeeRatesSats;
|
return targetBlocksFeeRatesSats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Double getNextBlockMedianFeeRate() {
|
||||||
|
FeeRatesSource feeRatesSource = Config.get().getFeeRatesSource();
|
||||||
|
feeRatesSource = (feeRatesSource == null ? FeeRatesSource.MEMPOOL_SPACE : feeRatesSource);
|
||||||
|
if(feeRatesSource.supportsNetwork(Network.get())) {
|
||||||
|
try {
|
||||||
|
return feeRatesSource.getNextBlockMedianFeeRate();
|
||||||
|
} catch(Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public Map<Integer, Double> getDefaultFeeEstimates(List<Integer> targetBlocks) throws ServerException {
|
public Map<Integer, Double> getDefaultFeeEstimates(List<Integer> targetBlocks) throws ServerException {
|
||||||
try {
|
try {
|
||||||
Map<Integer, Double> targetBlocksFeeRatesBtcKb = electrumServerRpc.getFeeEstimates(getTransport(), targetBlocks);
|
Map<Integer, Double> targetBlocksFeeRatesBtcKb = electrumServerRpc.getFeeEstimates(getTransport(), targetBlocks);
|
||||||
|
|
@ -1460,8 +1474,9 @@ public class ElectrumServer {
|
||||||
if(elapsed > FEE_RATES_PERIOD) {
|
if(elapsed > FEE_RATES_PERIOD) {
|
||||||
Map<Integer, Double> blockTargetFeeRates = electrumServer.getFeeEstimates(AppServices.TARGET_BLOCKS_RANGE, false);
|
Map<Integer, Double> blockTargetFeeRates = electrumServer.getFeeEstimates(AppServices.TARGET_BLOCKS_RANGE, false);
|
||||||
Set<MempoolRateSize> mempoolRateSizes = electrumServer.getMempoolRateSizes();
|
Set<MempoolRateSize> mempoolRateSizes = electrumServer.getMempoolRateSizes();
|
||||||
|
Double nextBlockMedianFeeRate = electrumServer.getNextBlockMedianFeeRate();
|
||||||
feeRatesRetrievedAt = System.currentTimeMillis();
|
feeRatesRetrievedAt = System.currentTimeMillis();
|
||||||
return new FeeRatesUpdatedEvent(blockTargetFeeRates, mempoolRateSizes);
|
return new FeeRatesUpdatedEvent(blockTargetFeeRates, mempoolRateSizes, nextBlockMedianFeeRate);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
closeConnection();
|
closeConnection();
|
||||||
|
|
@ -1939,7 +1954,8 @@ public class ElectrumServer {
|
||||||
protected FeeRatesUpdatedEvent call() throws ServerException {
|
protected FeeRatesUpdatedEvent call() throws ServerException {
|
||||||
ElectrumServer electrumServer = new ElectrumServer();
|
ElectrumServer electrumServer = new ElectrumServer();
|
||||||
Map<Integer, Double> blockTargetFeeRates = electrumServer.getFeeEstimates(AppServices.TARGET_BLOCKS_RANGE, false);
|
Map<Integer, Double> blockTargetFeeRates = electrumServer.getFeeEstimates(AppServices.TARGET_BLOCKS_RANGE, false);
|
||||||
return new FeeRatesUpdatedEvent(blockTargetFeeRates, null);
|
Double nextBlockMedianFeeRate = electrumServer.getNextBlockMedianFeeRate();
|
||||||
|
return new FeeRatesUpdatedEvent(blockTargetFeeRates, null, nextBlockMedianFeeRate);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -1989,7 +2005,11 @@ public class ElectrumServer {
|
||||||
subscribeRecent(electrumServer);
|
subscribeRecent(electrumServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new BlockSummaryEvent(blockSummaryMap);
|
Double nextBlockMedianFeeRate = null;
|
||||||
|
if(!isBlockstorm(totalBlocks)) {
|
||||||
|
nextBlockMedianFeeRate = electrumServer.getNextBlockMedianFeeRate();
|
||||||
|
}
|
||||||
|
return new BlockSummaryEvent(blockSummaryMap, nextBlockMedianFeeRate);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -2054,7 +2074,7 @@ public class ElectrumServer {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
broadcastService.setDelay(Duration.seconds(Math.random() * 60 ));
|
broadcastService.setDelay(Duration.seconds(Math.random() * 60 * 10));
|
||||||
broadcastService.setPeriod(Duration.hours(1));
|
broadcastService.setPeriod(Duration.hours(1));
|
||||||
broadcastService.setOnSucceeded(_ -> broadcastService.cancel());
|
broadcastService.setOnSucceeded(_ -> broadcastService.cancel());
|
||||||
broadcastService.setOnFailed(_ -> broadcastService.cancel());
|
broadcastService.setOnFailed(_ -> broadcastService.cancel());
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,12 @@ public enum FeeRatesSource {
|
||||||
return getThreeTierFeeRates(this, defaultblockTargetFeeRates, url);
|
return getThreeTierFeeRates(this, defaultblockTargetFeeRates, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Double getNextBlockMedianFeeRate() throws Exception {
|
||||||
|
String url = getApiUrl() + "v1/fees/mempool-blocks";
|
||||||
|
return requestNextBlockMedianFeeRate(this, url);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BlockSummary getBlockSummary(Sha256Hash blockId) throws Exception {
|
public BlockSummary getBlockSummary(Sha256Hash blockId) throws Exception {
|
||||||
String url = getApiUrl() + "v1/block/" + Utils.bytesToHex(blockId.getReversedBytes());
|
String url = getApiUrl() + "v1/block/" + Utils.bytesToHex(blockId.getReversedBytes());
|
||||||
|
|
@ -130,6 +136,10 @@ public enum FeeRatesSource {
|
||||||
|
|
||||||
public abstract Map<Integer, Double> getBlockTargetFeeRates(Map<Integer, Double> defaultblockTargetFeeRates);
|
public abstract Map<Integer, Double> getBlockTargetFeeRates(Map<Integer, Double> defaultblockTargetFeeRates);
|
||||||
|
|
||||||
|
public Double getNextBlockMedianFeeRate() throws Exception {
|
||||||
|
throw new UnsupportedOperationException(name + " does not support retrieving the next block median fee rate");
|
||||||
|
}
|
||||||
|
|
||||||
public BlockSummary getBlockSummary(Sha256Hash blockId) throws Exception {
|
public BlockSummary getBlockSummary(Sha256Hash blockId) throws Exception {
|
||||||
throw new UnsupportedOperationException(name + " does not support block summaries");
|
throw new UnsupportedOperationException(name + " does not support block summaries");
|
||||||
}
|
}
|
||||||
|
|
@ -199,6 +209,30 @@ public enum FeeRatesSource {
|
||||||
return httpClientService.requestJson(url, ThreeTierRates.class, null);
|
return httpClientService.requestJson(url, ThreeTierRates.class, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static Double requestNextBlockMedianFeeRate(FeeRatesSource feeRatesSource, String url) throws Exception {
|
||||||
|
if(log.isInfoEnabled()) {
|
||||||
|
log.info("Requesting next block median fee rate from " + url);
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpClientService httpClientService = AppServices.getHttpClientService();
|
||||||
|
try {
|
||||||
|
MempoolBlock[] mempoolBlocks = feeRatesSource.requestMempoolBlocks(url, httpClientService);
|
||||||
|
return mempoolBlocks.length > 0 ? mempoolBlocks[0].medianFee : null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
if(log.isDebugEnabled()) {
|
||||||
|
log.warn("Error retrieving next block median fee rate from " + url, e);
|
||||||
|
} else {
|
||||||
|
log.warn("Error retrieving next block median fee rate from " + url + " (" + e.getMessage() + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MempoolBlock[] requestMempoolBlocks(String url, HttpClientService httpClientService) throws Exception {
|
||||||
|
return httpClientService.requestJson(url, MempoolBlock[].class, null);
|
||||||
|
}
|
||||||
|
|
||||||
protected static BlockSummary requestBlockSummary(FeeRatesSource feeRatesSource, String url) throws Exception {
|
protected static BlockSummary requestBlockSummary(FeeRatesSource feeRatesSource, String url) throws Exception {
|
||||||
if(log.isInfoEnabled()) {
|
if(log.isInfoEnabled()) {
|
||||||
log.info("Requesting block summary from " + url);
|
log.info("Requesting block summary from " + url);
|
||||||
|
|
@ -309,6 +343,8 @@ public enum FeeRatesSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected record MempoolBlock(Integer nTx, Double medianFee) {}
|
||||||
|
|
||||||
protected record MempoolBlockSummary(String id, Integer height, Long timestamp, Integer tx_count, Integer weight, MempoolBlockSummaryExtras extras) {
|
protected record MempoolBlockSummary(String id, Integer height, Long timestamp, Integer tx_count, Integer weight, MempoolBlockSummaryExtras extras) {
|
||||||
public Double getMedianFee() {
|
public Double getMedianFee() {
|
||||||
return extras == null ? null : extras.medianFee();
|
return extras == null ? null : extras.medianFee();
|
||||||
|
|
|
||||||
|
|
@ -326,7 +326,7 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
recentBlocksView.visibleProperty().bind(Bindings.equal(feeRatesSelectionProperty, FeeRatesSelection.RECENT_BLOCKS));
|
recentBlocksView.visibleProperty().bind(Bindings.equal(feeRatesSelectionProperty, FeeRatesSelection.RECENT_BLOCKS));
|
||||||
List<BlockSummary> blockSummaries = AppServices.getBlockSummaries().values().stream().sorted().toList();
|
List<BlockSummary> blockSummaries = AppServices.getBlockSummaries().values().stream().sorted().toList();
|
||||||
if(!blockSummaries.isEmpty()) {
|
if(!blockSummaries.isEmpty()) {
|
||||||
recentBlocksView.update(blockSummaries, AppServices.getDefaultFeeRate());
|
recentBlocksView.update(blockSummaries, AppServices.getNextBlockMedianFeeRate());
|
||||||
}
|
}
|
||||||
|
|
||||||
feeRatesSelectionProperty.addListener((_, oldValue, newValue) -> {
|
feeRatesSelectionProperty.addListener((_, oldValue, newValue) -> {
|
||||||
|
|
@ -1411,7 +1411,12 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
setFeeRatePriority(getFeeRangeRate());
|
setFeeRatePriority(getFeeRangeRate());
|
||||||
}
|
}
|
||||||
feeRange.updateTrackHighlight();
|
feeRange.updateTrackHighlight();
|
||||||
recentBlocksView.updateFeeRate(event.getTargetBlockFeeRates());
|
|
||||||
|
if(event.getNextBlockMedianFeeRate() != null) {
|
||||||
|
recentBlocksView.updateFeeRate(event.getNextBlockMedianFeeRate());
|
||||||
|
} else {
|
||||||
|
recentBlocksView.updateFeeRate(event.getTargetBlockFeeRates());
|
||||||
|
}
|
||||||
|
|
||||||
if(updateDefaultFeeRate) {
|
if(updateDefaultFeeRate) {
|
||||||
if(getFeeRate() != null && Long.valueOf((long)getFallbackFeeRate()).equals(getFeeRate().longValue())) {
|
if(getFeeRate() != null && Long.valueOf((long)getFallbackFeeRate()).equals(getFeeRate().longValue())) {
|
||||||
|
|
@ -1435,7 +1440,7 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void blockSummary(BlockSummaryEvent event) {
|
public void blockSummary(BlockSummaryEvent event) {
|
||||||
Platform.runLater(() -> recentBlocksView.update(AppServices.getBlockSummaries().values().stream().sorted().toList(), AppServices.getDefaultFeeRate()));
|
Platform.runLater(() -> recentBlocksView.update(AppServices.getBlockSummaries().values().stream().sorted().toList(), AppServices.getNextBlockMedianFeeRate()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue