mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-11-05 05:46:44 +00:00
optimize fetching mempool entries for fee histogram when connected to bitcoin core, fix and improve mempool fee rates chart
This commit is contained in:
parent
3242f00812
commit
171bf24133
17 changed files with 326 additions and 53 deletions
|
@ -669,6 +669,10 @@ public class AppServices {
|
|||
}
|
||||
|
||||
private void addMempoolRateSizes(Set<MempoolRateSize> rateSizes) {
|
||||
if(rateSizes.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LocalDateTime dateMinute = LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES);
|
||||
if(mempoolHistogram.isEmpty()) {
|
||||
mempoolHistogram.put(Date.from(dateMinute.minusMinutes(1).atZone(ZoneId.systemDefault()).toInstant()), rateSizes);
|
||||
|
@ -1022,8 +1026,6 @@ public class AppServices {
|
|||
public void newConnection(ConnectionEvent event) {
|
||||
currentBlockHeight = event.getBlockHeight();
|
||||
System.setProperty(Network.BLOCK_HEIGHT_PROPERTY, Integer.toString(currentBlockHeight));
|
||||
targetBlockFeeRates = event.getTargetBlockFeeRates();
|
||||
addMempoolRateSizes(event.getMempoolRateSizes());
|
||||
minimumRelayFeeRate = Math.max(event.getMinimumRelayFeeRate(), Transaction.DEFAULT_MIN_RELAY_FEE);
|
||||
latestBlockHeader = event.getBlockHeader();
|
||||
Config.get().addRecentServer();
|
||||
|
@ -1046,6 +1048,10 @@ public class AppServices {
|
|||
@Subscribe
|
||||
public void feesUpdated(FeeRatesUpdatedEvent event) {
|
||||
targetBlockFeeRates = event.getTargetBlockFeeRates();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void mempoolRateSizes(MempoolRateSizesUpdatedEvent event) {
|
||||
addMempoolRateSizes(event.getMempoolRateSizes());
|
||||
}
|
||||
|
||||
|
|
|
@ -1,19 +1,31 @@
|
|||
package com.sparrowwallet.sparrow.control;
|
||||
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.Theme;
|
||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||
import com.sparrowwallet.sparrow.io.Config;
|
||||
import com.sparrowwallet.sparrow.net.MempoolRateSize;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.NamedArg;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.chart.*;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.util.Duration;
|
||||
import javafx.util.StringConverter;
|
||||
import org.controlsfx.glyphfont.Glyph;
|
||||
|
@ -29,14 +41,80 @@ public class MempoolSizeFeeRatesChart extends StackedAreaChart<String, Number> {
|
|||
private static final DateFormat dateFormatter = new SimpleDateFormat("HH:mm");
|
||||
public static final int MAX_PERIOD_HOURS = 2;
|
||||
private static final double Y_VALUE_BREAK_MVB = 3.0;
|
||||
private static final List<Integer> FEE_RATES_INTERVALS = List.of(1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200, 250, 300, 350, 400, 500, 600, 700, 800);
|
||||
|
||||
private Tooltip tooltip;
|
||||
|
||||
private MempoolSizeFeeRatesChart expandedChart;
|
||||
private final EventHandler<MouseEvent> expandedChartHandler = new EventHandler<>() {
|
||||
@Override
|
||||
public void handle(MouseEvent event) {
|
||||
if(!event.isConsumed() && event.getButton() != MouseButton.SECONDARY) {
|
||||
Stage stage = new Stage(StageStyle.UNDECORATED);
|
||||
stage.setTitle("Mempool by vBytes");
|
||||
stage.initOwner(MempoolSizeFeeRatesChart.this.getScene().getWindow());
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.setResizable(false);
|
||||
|
||||
StackPane scenePane = new StackPane();
|
||||
if(org.controlsfx.tools.Platform.getCurrent() == org.controlsfx.tools.Platform.WINDOWS) {
|
||||
scenePane.setBorder(new Border(new BorderStroke(Color.DARKGRAY, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderWidths.DEFAULT)));
|
||||
}
|
||||
|
||||
scenePane.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm());
|
||||
if(Config.get().getTheme() == Theme.DARK) {
|
||||
scenePane.getStylesheets().add(AppServices.class.getResource("darktheme.css").toExternalForm());
|
||||
}
|
||||
scenePane.getStylesheets().add(AppServices.class.getResource("wallet/wallet.css").toExternalForm());
|
||||
scenePane.getStylesheets().add(AppServices.class.getResource("wallet/send.css").toExternalForm());
|
||||
|
||||
VBox vBox = new VBox(20);
|
||||
vBox.setPadding(new Insets(20, 20, 20, 20));
|
||||
|
||||
expandedChart = new MempoolSizeFeeRatesChart();
|
||||
expandedChart.initialize();
|
||||
expandedChart.getStyleClass().add("vsizeChart");
|
||||
expandedChart.update(AppServices.getMempoolHistogram());
|
||||
expandedChart.setLegendVisible(false);
|
||||
expandedChart.setAnimated(false);
|
||||
expandedChart.setPrefWidth(700);
|
||||
|
||||
HBox buttonBox = new HBox();
|
||||
buttonBox.setAlignment(Pos.CENTER_RIGHT);
|
||||
Button button = new Button("Close");
|
||||
button.setOnAction(e -> {
|
||||
stage.close();
|
||||
});
|
||||
buttonBox.getChildren().add(button);
|
||||
vBox.getChildren().addAll(expandedChart, buttonBox);
|
||||
scenePane.getChildren().add(vBox);
|
||||
|
||||
Scene scene = new Scene(scenePane);
|
||||
AppServices.onEscapePressed(scene, stage::close);
|
||||
AppServices.setStageIcon(stage);
|
||||
stage.setScene(scene);
|
||||
stage.setOnShowing(e -> {
|
||||
AppServices.moveToActiveWindowScreen(stage, 800, 460);
|
||||
});
|
||||
stage.setOnHidden(e -> {
|
||||
expandedChart = null;
|
||||
});
|
||||
stage.show();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public MempoolSizeFeeRatesChart() {
|
||||
super(new CategoryAxis(), new NumberAxis());
|
||||
}
|
||||
|
||||
public MempoolSizeFeeRatesChart(@NamedArg("xAxis") Axis<String> xAxis, @NamedArg("yAxis") Axis<Number> yAxis) {
|
||||
super(xAxis, yAxis);
|
||||
setOnMouseClicked(expandedChartHandler);
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
getStyleClass().add("vsizeChart");
|
||||
setCreateSymbols(false);
|
||||
setCursor(Cursor.CROSSHAIR);
|
||||
setVerticalGridLinesVisible(false);
|
||||
|
@ -78,17 +156,18 @@ public class MempoolSizeFeeRatesChart extends StackedAreaChart<String, Number> {
|
|||
}
|
||||
});
|
||||
|
||||
long previousFeeRate = 0;
|
||||
for(Long feeRate : AppServices.FEE_RATES_RANGE) {
|
||||
for(int i = 0; i < FEE_RATES_INTERVALS.size(); i++) {
|
||||
int feeRate = FEE_RATES_INTERVALS.get(i);
|
||||
int nextFeeRate = (i == FEE_RATES_INTERVALS.size() - 1 ? Integer.MAX_VALUE : FEE_RATES_INTERVALS.get(i+1));
|
||||
XYChart.Series<String, Number> series = new XYChart.Series<>();
|
||||
series.setName(feeRate + "+ sats/vB");
|
||||
series.setName(feeRate + "-" + (nextFeeRate == Integer.MAX_VALUE ? 900 : nextFeeRate));
|
||||
long seriesTotalVSize = 0;
|
||||
|
||||
for(Date date : periodRateSizes.keySet()) {
|
||||
Set<MempoolRateSize> rateSizes = periodRateSizes.get(date);
|
||||
long totalVSize = 0;
|
||||
for(MempoolRateSize rateSize : rateSizes) {
|
||||
if(rateSize.getFee() > previousFeeRate && rateSize.getFee() <= feeRate) {
|
||||
if(rateSize.getFee() >= feeRate && rateSize.getFee() < nextFeeRate) {
|
||||
totalVSize += rateSize.getVSize();
|
||||
}
|
||||
}
|
||||
|
@ -100,8 +179,19 @@ public class MempoolSizeFeeRatesChart extends StackedAreaChart<String, Number> {
|
|||
if(seriesTotalVSize > 0) {
|
||||
getData().add(series);
|
||||
}
|
||||
}
|
||||
|
||||
previousFeeRate = feeRate;
|
||||
for(int i = 0; i < getData().size(); i++) {
|
||||
Series<String, Number> series = getData().get(i);
|
||||
Set<Node> nodes = lookupAll(".series" + i);
|
||||
for(Node node : nodes) {
|
||||
if(node.getStyleClass().contains("chart-series-area-line")) {
|
||||
node.setStyle("-fx-stroke: VSIZE" + series.getName() + "_COLOR; -fx-opacity: 0.2;");
|
||||
} else {
|
||||
node.setStyle("-fx-fill: VSIZE" + series.getName() + "_COLOR; -fx-opacity: 0.5;");
|
||||
}
|
||||
node.getStyleClass().remove("default-color" + i);
|
||||
}
|
||||
}
|
||||
|
||||
final double maxMvB = getMaxMvB(getData());
|
||||
|
@ -131,6 +221,10 @@ public class MempoolSizeFeeRatesChart extends StackedAreaChart<String, Number> {
|
|||
numberAxis.setTickLabelsVisible(false);
|
||||
numberAxis.setOpacity(0);
|
||||
}
|
||||
|
||||
if(expandedChart != null) {
|
||||
expandedChart.update(mempoolRateSizes);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Date, Set<MempoolRateSize>> getPeriodRateSizes(Map<Date, Set<MempoolRateSize>> mempoolRateSizes) {
|
||||
|
@ -200,11 +294,9 @@ public class MempoolSizeFeeRatesChart extends StackedAreaChart<String, Number> {
|
|||
double mvb = kvb / 1000;
|
||||
if(mvb >= 0.01 || (maxMvB < Y_VALUE_BREAK_MVB && mvb > 0.001)) {
|
||||
String amount = (maxMvB < Y_VALUE_BREAK_MVB ? (int)kvb + " kvB" : String.format("%.2f", mvb) + " MvB");
|
||||
Label label = new Label(series.getName() + ": " + amount);
|
||||
Label label = new Label(series.getName() + " sats/vB: " + amount);
|
||||
Glyph circle = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.CIRCLE);
|
||||
if(i < 8) {
|
||||
circle.setStyle("-fx-text-fill: CHART_COLOR_" + (i+1));
|
||||
}
|
||||
circle.setStyle("-fx-text-fill: VSIZE" + series.getName() + "_COLOR; -fx-opacity: 0.7;");
|
||||
label.setGraphic(circle);
|
||||
getChildren().add(label);
|
||||
}
|
||||
|
|
|
@ -5,20 +5,15 @@ import com.sparrowwallet.sparrow.net.MempoolRateSize;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class FeeRatesUpdatedEvent {
|
||||
public class FeeRatesUpdatedEvent extends MempoolRateSizesUpdatedEvent {
|
||||
private final Map<Integer, Double> targetBlockFeeRates;
|
||||
private final Set<MempoolRateSize> mempoolRateSizes;
|
||||
|
||||
public FeeRatesUpdatedEvent(Map<Integer, Double> targetBlockFeeRates, Set<MempoolRateSize> mempoolRateSizes) {
|
||||
super(mempoolRateSizes);
|
||||
this.targetBlockFeeRates = targetBlockFeeRates;
|
||||
this.mempoolRateSizes = mempoolRateSizes;
|
||||
}
|
||||
|
||||
public Map<Integer, Double> getTargetBlockFeeRates() {
|
||||
return targetBlockFeeRates;
|
||||
}
|
||||
|
||||
public Set<MempoolRateSize> getMempoolRateSizes() {
|
||||
return mempoolRateSizes;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package com.sparrowwallet.sparrow.event;
|
||||
|
||||
/**
|
||||
* The event is posted when the first set of mempool entries (txid and vsizes) have been retrieved from the node.
|
||||
* Cormorant only.
|
||||
*/
|
||||
public class MempoolEntriesInitializedEvent {
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package com.sparrowwallet.sparrow.event;
|
||||
|
||||
import com.sparrowwallet.sparrow.net.MempoolRateSize;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class MempoolRateSizesUpdatedEvent {
|
||||
private final Set<MempoolRateSize> mempoolRateSizes;
|
||||
|
||||
public MempoolRateSizesUpdatedEvent(Set<MempoolRateSize> mempoolRateSizes) {
|
||||
this.mempoolRateSizes = mempoolRateSizes;
|
||||
}
|
||||
|
||||
public Set<MempoolRateSize> getMempoolRateSizes() {
|
||||
return mempoolRateSizes;
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ import com.sparrowwallet.sparrow.event.WalletHistoryStatusEvent;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -235,16 +235,16 @@ public class BatchedElectrumServerRpc implements ElectrumServerRpc {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Map<Long, Long> getFeeRateHistogram(Transport transport) {
|
||||
public Map<Double, Long> getFeeRateHistogram(Transport transport) {
|
||||
try {
|
||||
JsonRpcClient client = new JsonRpcClient(transport);
|
||||
BigInteger[][] feesArray = new RetryLogic<BigInteger[][]>(DEFAULT_MAX_ATTEMPTS, RETRY_DELAY_SECS, IllegalStateException.class).getResult(() ->
|
||||
client.createRequest().returnAs(BigInteger[][].class).method("mempool.get_fee_histogram").id(idCounter.incrementAndGet()).execute());
|
||||
BigDecimal[][] feesArray = new RetryLogic<BigDecimal[][]>(DEFAULT_MAX_ATTEMPTS, RETRY_DELAY_SECS, IllegalStateException.class).getResult(() ->
|
||||
client.createRequest().returnAs(BigDecimal[][].class).method("mempool.get_fee_histogram").id(idCounter.incrementAndGet()).execute());
|
||||
|
||||
Map<Long, Long> feeRateHistogram = new TreeMap<>();
|
||||
for(BigInteger[] feePair : feesArray) {
|
||||
Map<Double, Long> feeRateHistogram = new TreeMap<>();
|
||||
for(BigDecimal[] feePair : feesArray) {
|
||||
if(feePair[0].longValue() > 0) {
|
||||
feeRateHistogram.put(feePair[0].longValue(), feePair[1].longValue());
|
||||
feeRateHistogram.put(feePair[0].doubleValue(), feePair[1].longValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -836,9 +836,9 @@ public class ElectrumServer {
|
|||
}
|
||||
|
||||
public Set<MempoolRateSize> getMempoolRateSizes() throws ServerException {
|
||||
Map<Long, Long> feeRateHistogram = electrumServerRpc.getFeeRateHistogram(getTransport());
|
||||
Map<Double, Long> feeRateHistogram = electrumServerRpc.getFeeRateHistogram(getTransport());
|
||||
Set<MempoolRateSize> mempoolRateSizes = new TreeSet<>();
|
||||
for(Long fee : feeRateHistogram.keySet()) {
|
||||
for(Double fee : feeRateHistogram.keySet()) {
|
||||
mempoolRateSizes.add(new MempoolRateSize(fee, feeRateHistogram.get(fee)));
|
||||
}
|
||||
|
||||
|
@ -1331,6 +1331,13 @@ public class ElectrumServer {
|
|||
bwtStartLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void mempoolEntriesInitialized(MempoolEntriesInitializedEvent event) throws ServerException {
|
||||
ElectrumServer electrumServer = new ElectrumServer();
|
||||
Set<MempoolRateSize> mempoolRateSizes = electrumServer.getMempoolRateSizes();
|
||||
EventManager.get().post(new MempoolRateSizesUpdatedEvent(mempoolRateSizes));
|
||||
}
|
||||
}
|
||||
|
||||
public static class ReadRunnable implements Runnable {
|
||||
|
|
|
@ -30,7 +30,7 @@ public interface ElectrumServerRpc {
|
|||
|
||||
Map<Integer, Double> getFeeEstimates(Transport transport, List<Integer> targetBlocks);
|
||||
|
||||
Map<Long, Long> getFeeRateHistogram(Transport transport);
|
||||
Map<Double, Long> getFeeRateHistogram(Transport transport);
|
||||
|
||||
Double getMinimumRelayFee(Transport transport);
|
||||
|
||||
|
|
|
@ -3,15 +3,15 @@ package com.sparrowwallet.sparrow.net;
|
|||
import java.util.Objects;
|
||||
|
||||
public class MempoolRateSize implements Comparable<MempoolRateSize> {
|
||||
private final long fee;
|
||||
private final double fee;
|
||||
private final long vSize;
|
||||
|
||||
public MempoolRateSize(long fee, long vSize) {
|
||||
public MempoolRateSize(double fee, long vSize) {
|
||||
this.fee = fee;
|
||||
this.vSize = vSize;
|
||||
}
|
||||
|
||||
public long getFee() {
|
||||
public double getFee() {
|
||||
return fee;
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ public class MempoolRateSize implements Comparable<MempoolRateSize> {
|
|||
|
||||
@Override
|
||||
public int compareTo(MempoolRateSize other) {
|
||||
return Long.compare(fee, other.fee);
|
||||
return Double.compare(fee, other.fee);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -13,7 +13,7 @@ import com.sparrowwallet.sparrow.event.WalletHistoryStatusEvent;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
|
@ -260,16 +260,16 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Map<Long, Long> getFeeRateHistogram(Transport transport) {
|
||||
public Map<Double, Long> getFeeRateHistogram(Transport transport) {
|
||||
try {
|
||||
JsonRpcClient client = new JsonRpcClient(transport);
|
||||
BigInteger[][] feesArray = new RetryLogic<BigInteger[][]>(MAX_RETRIES, RETRY_DELAY, IllegalStateException.class).getResult(() ->
|
||||
client.createRequest().returnAs(BigInteger[][].class).method("mempool.get_fee_histogram").id(idCounter.incrementAndGet()).execute());
|
||||
BigDecimal[][] feesArray = new RetryLogic<BigDecimal[][]>(MAX_RETRIES, RETRY_DELAY, IllegalStateException.class).getResult(() ->
|
||||
client.createRequest().returnAs(BigDecimal[][].class).method("mempool.get_fee_histogram").id(idCounter.incrementAndGet()).execute());
|
||||
|
||||
Map<Long, Long> feeRateHistogram = new TreeMap<>();
|
||||
for(BigInteger[] feePair : feesArray) {
|
||||
Map<Double, Long> feeRateHistogram = new TreeMap<>();
|
||||
for(BigDecimal[] feePair : feesArray) {
|
||||
if(feePair[0].longValue() > 0) {
|
||||
feeRateHistogram.put(feePair[0].longValue(), feePair[1].longValue());
|
||||
feeRateHistogram.put(feePair[0].doubleValue(), feePair[1].longValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ public class Cormorant {
|
|||
}
|
||||
|
||||
public Server start() throws CormorantBitcoindException {
|
||||
bitcoindClient = new BitcoindClient();
|
||||
bitcoindClient = new BitcoindClient(useWallets);
|
||||
bitcoindClient.initialize();
|
||||
|
||||
Thread importThread = new Thread(() -> {
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.sparrowwallet.sparrow.net.cormorant.bitcoind;
|
|||
|
||||
import com.github.arteam.simplejsonrpc.client.JsonRpcClient;
|
||||
import com.github.arteam.simplejsonrpc.client.exception.JsonRpcException;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.sparrowwallet.drongo.KeyPurpose;
|
||||
import com.sparrowwallet.drongo.OutputDescriptor;
|
||||
import com.sparrowwallet.drongo.Utils;
|
||||
|
@ -25,11 +26,14 @@ import com.sparrowwallet.sparrow.net.cormorant.electrum.ScriptHashStatus;
|
|||
import com.sparrowwallet.sparrow.net.cormorant.index.Store;
|
||||
import com.sparrowwallet.drongo.protocol.*;
|
||||
import javafx.application.Platform;
|
||||
import javafx.concurrent.Service;
|
||||
import javafx.concurrent.Task;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
@ -60,6 +64,7 @@ public class BitcoindClient {
|
|||
|
||||
private Exception lastPollException;
|
||||
|
||||
private final boolean useWallets;
|
||||
private boolean pruned;
|
||||
private boolean legacyWalletExists;
|
||||
|
||||
|
@ -76,7 +81,11 @@ public class BitcoindClient {
|
|||
|
||||
private final List<String> pruneWarnedDescriptors = new ArrayList<>();
|
||||
|
||||
public BitcoindClient() {
|
||||
private final Map<String, MempoolEntry> mempoolEntries = new ConcurrentHashMap<>();
|
||||
private MempoolEntriesState mempoolEntriesState = MempoolEntriesState.UNINITIALIZED;
|
||||
private long timerTaskCount;
|
||||
|
||||
public BitcoindClient(boolean useWallets) {
|
||||
BitcoindTransport bitcoindTransport;
|
||||
|
||||
Config config = Config.get();
|
||||
|
@ -87,6 +96,7 @@ public class BitcoindClient {
|
|||
}
|
||||
|
||||
this.jsonRpcClient = new JsonRpcClient(bitcoindTransport);
|
||||
this.useWallets = useWallets;
|
||||
}
|
||||
|
||||
public void initialize() throws CormorantBitcoindException {
|
||||
|
@ -516,6 +526,64 @@ public class BitcoindClient {
|
|||
}
|
||||
}
|
||||
|
||||
public void initializeMempoolEntries() {
|
||||
mempoolEntriesState = MempoolEntriesState.INITIALIZING;
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
Set<String> txids = getBitcoindService().getRawMempool();
|
||||
long end = System.currentTimeMillis();
|
||||
|
||||
if(end - start < 1000) {
|
||||
//Fast system, fetch all mempool data at once
|
||||
mempoolEntries.putAll(getBitcoindService().getRawMempool(true));
|
||||
} else {
|
||||
//Slow system, fetch mempool entries one-by-one to avoid risking a node crash
|
||||
for(String txid : txids) {
|
||||
try {
|
||||
MempoolEntry mempoolEntry = getBitcoindService().getMempoolEntry(txid);
|
||||
mempoolEntries.put(txid, mempoolEntry);
|
||||
} catch(JsonRpcException e) {
|
||||
//ignore, probably tx has been removed from mempool
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mempoolEntriesState = MempoolEntriesState.INITIALIZED;
|
||||
}
|
||||
|
||||
public void updateMempoolEntries() {
|
||||
Set<String> txids = getBitcoindService().getRawMempool();
|
||||
|
||||
Set<String> removed = new HashSet<>(Sets.difference(mempoolEntries.keySet(), txids));
|
||||
mempoolEntries.keySet().removeAll(removed);
|
||||
|
||||
Set<String> added = Sets.difference(txids, mempoolEntries.keySet());
|
||||
for(String txid : added) {
|
||||
try {
|
||||
MempoolEntry mempoolEntry = getBitcoindService().getMempoolEntry(txid);
|
||||
mempoolEntries.put(txid, mempoolEntry);
|
||||
} catch(JsonRpcException e) {
|
||||
//ignore, probably tx has been removed from mempool
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, MempoolEntry> getMempoolEntries() {
|
||||
return mempoolEntries;
|
||||
}
|
||||
|
||||
public MempoolEntriesState getMempoolEntriesState() {
|
||||
return mempoolEntriesState;
|
||||
}
|
||||
|
||||
public InitializeMempoolEntriesService getInitializeMempoolEntriesService() {
|
||||
return new InitializeMempoolEntriesService();
|
||||
}
|
||||
|
||||
public boolean isUseWallets() {
|
||||
return useWallets;
|
||||
}
|
||||
|
||||
public Store getStore() {
|
||||
return store;
|
||||
}
|
||||
|
@ -566,6 +634,10 @@ public class BitcoindClient {
|
|||
}
|
||||
}
|
||||
|
||||
if(mempoolEntriesState == MempoolEntriesState.INITIALIZED && (++timerTaskCount+1) % 12 == 0) {
|
||||
updateMempoolEntries();
|
||||
}
|
||||
|
||||
ListSinceBlock listSinceBlock = getListSinceBlock(lastBlock);
|
||||
String currentBlock = lastBlock;
|
||||
updateStore(listSinceBlock);
|
||||
|
@ -591,7 +663,7 @@ public class BitcoindClient {
|
|||
}
|
||||
} catch(Exception e) {
|
||||
lastPollException = e;
|
||||
log.warn("Error polling Bitcoin Core: " + e.getMessage());
|
||||
log.warn("Error polling Bitcoin Core", e);
|
||||
|
||||
if(syncing) {
|
||||
syncingLock.lock();
|
||||
|
@ -627,4 +699,21 @@ public class BitcoindClient {
|
|||
return rescanSince == null ? "now" : rescanSince.getTime() / 1000;
|
||||
}
|
||||
}
|
||||
|
||||
public class InitializeMempoolEntriesService extends Service<Void> {
|
||||
@Override
|
||||
protected Task<Void> createTask() {
|
||||
return new Task<>() {
|
||||
@Override
|
||||
protected Void call() {
|
||||
initializeMempoolEntries();
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public enum MempoolEntriesState {
|
||||
UNINITIALIZED, INITIALIZING, INITIALIZED
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcService;
|
|||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@JsonRpcService
|
||||
@JsonRpcParams(ParamsType.ARRAY)
|
||||
|
@ -22,6 +23,9 @@ public interface BitcoindClientService {
|
|||
@JsonRpcMethod("estimatesmartfee")
|
||||
FeeInfo estimateSmartFee(@JsonRpcParam("conf_target") int blocks);
|
||||
|
||||
@JsonRpcMethod("getrawmempool")
|
||||
Set<String> getRawMempool();
|
||||
|
||||
@JsonRpcMethod("getrawmempool")
|
||||
Map<String, MempoolEntry> getRawMempool(@JsonRpcParam("verbose") boolean verbose);
|
||||
|
||||
|
|
|
@ -5,7 +5,10 @@ import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcMethod;
|
|||
import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcOptional;
|
||||
import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcParam;
|
||||
import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcService;
|
||||
import com.sparrowwallet.drongo.protocol.Transaction;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.SparrowWallet;
|
||||
import com.sparrowwallet.sparrow.event.MempoolEntriesInitializedEvent;
|
||||
import com.sparrowwallet.sparrow.net.Version;
|
||||
import com.sparrowwallet.sparrow.net.cormorant.Cormorant;
|
||||
import com.sparrowwallet.sparrow.net.cormorant.bitcoind.*;
|
||||
|
@ -60,9 +63,23 @@ public class ElectrumServerService {
|
|||
}
|
||||
|
||||
@JsonRpcMethod("mempool.get_fee_histogram")
|
||||
public List<List<Number>> getFeeHistogram() throws BitcoindIOException {
|
||||
try {
|
||||
Map<String, MempoolEntry> mempoolEntries = bitcoindClient.getBitcoindService().getRawMempool(true);
|
||||
public List<List<Number>> getFeeHistogram() {
|
||||
BitcoindClient.MempoolEntriesState mempoolEntriesState = bitcoindClient.getMempoolEntriesState();
|
||||
if(mempoolEntriesState != BitcoindClient.MempoolEntriesState.INITIALIZED) {
|
||||
if(bitcoindClient.isUseWallets() && mempoolEntriesState == BitcoindClient.MempoolEntriesState.UNINITIALIZED) {
|
||||
BitcoindClient.InitializeMempoolEntriesService initializeMempoolEntriesService = bitcoindClient.getInitializeMempoolEntriesService();
|
||||
initializeMempoolEntriesService.setOnSucceeded(successEvent -> {
|
||||
EventManager.get().post(new MempoolEntriesInitializedEvent());
|
||||
});
|
||||
initializeMempoolEntriesService.setOnFailed(failedEvent -> {
|
||||
log.error("Failed to initialize mempool entries", failedEvent.getSource().getException());
|
||||
});
|
||||
initializeMempoolEntriesService.start();
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
Map<String, MempoolEntry> mempoolEntries = bitcoindClient.getMempoolEntries();
|
||||
|
||||
List<VsizeFeerate> vsizeFeerates = mempoolEntries.values().stream().map(entry -> new VsizeFeerate(entry.vsize(), entry.fees().base())).sorted().toList();
|
||||
|
||||
|
@ -85,8 +102,6 @@ public class ElectrumServerService {
|
|||
}
|
||||
|
||||
return histogram;
|
||||
} catch(IllegalStateException e) {
|
||||
throw new BitcoindIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,7 +219,9 @@ public class ElectrumServerService {
|
|||
|
||||
public VsizeFeerate(int vsize, double fee) {
|
||||
this.vsize = vsize;
|
||||
this.feerate = fee / vsize * 100000000;
|
||||
double feeRate = fee / vsize * Transaction.SATOSHIS_PER_BITCOIN;
|
||||
//Round down to 0.1 sats/vb precision
|
||||
this.feerate = Math.floor(10 * feeRate) / 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1508,7 +1508,6 @@ public class SendController extends WalletFormController implements Initializabl
|
|||
public void feeRatesUpdated(FeeRatesUpdatedEvent event) {
|
||||
blockTargetFeeRatesChart.update(event.getTargetBlockFeeRates());
|
||||
blockTargetFeeRatesChart.select(getTargetBlocks());
|
||||
mempoolSizeFeeRatesChart.update(getMempoolHistogram());
|
||||
if(targetBlocksField.isVisible()) {
|
||||
setFeeRate(event.getTargetBlockFeeRates().get(getTargetBlocks()));
|
||||
} else {
|
||||
|
@ -1517,6 +1516,11 @@ public class SendController extends WalletFormController implements Initializabl
|
|||
addFeeRangeTrackHighlight(0);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void mempoolRateSizesUpdated(MempoolRateSizesUpdatedEvent event) {
|
||||
mempoolSizeFeeRatesChart.update(getMempoolHistogram());
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void feeRateSelectionChanged(FeeRatesSelectionChangedEvent event) {
|
||||
if(event.getWallet() == getWalletForm().getWallet()) {
|
||||
|
|
|
@ -16,6 +16,7 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class SparrowUtxoConfigPersister extends UtxoConfigPersister {
|
||||
|
@ -38,7 +39,7 @@ public class SparrowUtxoConfigPersister extends UtxoConfigPersister {
|
|||
Map<String, UtxoConfigPersisted> utxoConfigs = wallet.getUtxoMixes().entrySet().stream()
|
||||
.collect(Collectors.toMap(entry -> entry.getKey().toString(), entry -> new UtxoConfigPersisted(entry.getValue().getMixesDone(), entry.getValue().getExpired()),
|
||||
(u, v) -> { throw new IllegalStateException("Duplicate utxo config hashes"); },
|
||||
HashMap::new));
|
||||
ConcurrentHashMap::new));
|
||||
|
||||
return new UtxoConfigData(utxoConfigs);
|
||||
}
|
||||
|
|
|
@ -131,3 +131,36 @@
|
|||
#transactionDiagram .useradd-icon {
|
||||
-fx-text-fill: -fx-accent;
|
||||
}
|
||||
|
||||
.vsizeChart {
|
||||
VSIZE1-2_COLOR: rgb(216, 27, 96);
|
||||
VSIZE2-3_COLOR: rgb(142, 36, 170);
|
||||
VSIZE3-4_COLOR: rgb(94, 53, 177);
|
||||
VSIZE4-5_COLOR: rgb(57, 73, 171);
|
||||
VSIZE5-6_COLOR: rgb(30, 136, 229);
|
||||
VSIZE6-8_COLOR: rgb(3, 155, 229);
|
||||
VSIZE8-10_COLOR: rgb(0, 172, 193);
|
||||
VSIZE10-12_COLOR: rgb(0, 137, 123);
|
||||
VSIZE12-15_COLOR: rgb(67, 160, 71);
|
||||
VSIZE15-20_COLOR: rgb(124, 179, 66);
|
||||
VSIZE20-30_COLOR: rgb(192, 202, 51);
|
||||
VSIZE30-40_COLOR: rgb(253, 216, 53);
|
||||
VSIZE40-50_COLOR: rgb(255, 179, 0);
|
||||
VSIZE50-60_COLOR: rgb(251, 140, 0);
|
||||
VSIZE60-70_COLOR: rgb(244, 81, 30);
|
||||
VSIZE70-80_COLOR: rgb(109, 76, 65);
|
||||
VSIZE80-90_COLOR: rgb(117, 117, 117);
|
||||
VSIZE90-100_COLOR: rgb(84, 110, 122);
|
||||
VSIZE100-125_COLOR: rgb(183, 28, 28);
|
||||
VSIZE125-150_COLOR: rgb(136, 14, 79);
|
||||
VSIZE150-175_COLOR: rgb(74, 20, 140);
|
||||
VSIZE175-200_COLOR: rgb(49, 27, 146);
|
||||
VSIZE200-250_COLOR: rgb(26, 35, 126);
|
||||
VSIZE250-300_COLOR: rgb(13, 71, 161);
|
||||
VSIZE300-350_COLOR: rgb(1, 87, 155);
|
||||
VSIZE350-400_COLOR: rgb(0, 96, 100);
|
||||
VSIZE400-500_COLOR: rgb(0, 77, 64);
|
||||
VSIZE500-600_COLOR: rgb(27, 94, 32);
|
||||
VSIZE600-700_COLOR: rgb(51, 105, 30);
|
||||
VSIZE700-800_COLOR: rgb(130, 119, 23);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue