mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2026-08-01 04:26:14 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
231eb13cee | ||
|
|
52470ee6d8 | ||
|
|
853949675e | ||
|
|
098afebbe0 | ||
|
|
63c0a6d6e2 |
+1
-1
@@ -20,7 +20,7 @@ if(System.getProperty("os.arch") == "aarch64") {
|
||||
def headless = "true".equals(System.getProperty("java.awt.headless"))
|
||||
|
||||
group 'com.sparrowwallet'
|
||||
version '2.2.1'
|
||||
version '2.2.2'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
@@ -83,7 +83,7 @@ sudo apt install -y rpm fakeroot binutils
|
||||
First, assign a temporary variable in your shell for the specific release you want to build. For the current one specify:
|
||||
|
||||
```shell
|
||||
GIT_TAG="2.2.0"
|
||||
GIT_TAG="2.2.1"
|
||||
```
|
||||
|
||||
The project can then be initially cloned as follows:
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.2.1</string>
|
||||
<string>2.2.2</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<!-- See https://developer.apple.com/app-store/categories/ for list of AppStore categories -->
|
||||
|
||||
@@ -136,6 +136,8 @@ public class AppServices {
|
||||
|
||||
private static Map<Integer, Double> targetBlockFeeRates;
|
||||
|
||||
private static Double nextBlockMedianFeeRate;
|
||||
|
||||
private static final TreeMap<Date, Set<MempoolRateSize>> mempoolHistogram = new TreeMap<>();
|
||||
|
||||
private static Double minimumRelayFeeRate;
|
||||
@@ -748,6 +750,10 @@ public class AppServices {
|
||||
return Math.max(minRate, Transaction.DUST_RELAY_TX_FEE);
|
||||
}
|
||||
|
||||
public static Double getNextBlockMedianFeeRate() {
|
||||
return nextBlockMedianFeeRate == null ? getDefaultFeeRate() : nextBlockMedianFeeRate;
|
||||
}
|
||||
|
||||
public static double getFallbackFeeRate() {
|
||||
return Network.get() == Network.MAINNET ? FALLBACK_FEE_RATE : TESTNET_FALLBACK_FEE_RATE;
|
||||
}
|
||||
@@ -1249,11 +1255,13 @@ public class AppServices {
|
||||
if(AppServices.currentBlockHeight != null) {
|
||||
blockSummaries.keySet().removeIf(height -> AppServices.currentBlockHeight - height > 5);
|
||||
}
|
||||
nextBlockMedianFeeRate = event.getNextBlockMedianFeeRate();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void feesUpdated(FeeRatesUpdatedEvent event) {
|
||||
targetBlockFeeRates = event.getTargetBlockFeeRates();
|
||||
nextBlockMedianFeeRate = event.getNextBlockMedianFeeRate();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
|
||||
@@ -18,7 +18,7 @@ import java.util.*;
|
||||
public class SparrowWallet {
|
||||
public static final String APP_ID = "sparrow";
|
||||
public static final String APP_NAME = "Sparrow";
|
||||
public static final String APP_VERSION = "2.2.1";
|
||||
public static final String APP_VERSION = "2.2.2";
|
||||
public static final String APP_VERSION_SUFFIX = "";
|
||||
public static final String APP_HOME_PROPERTY = "sparrow.home";
|
||||
public static final String NETWORK_ENV_PROPERTY = "SPARROW_NETWORK";
|
||||
|
||||
@@ -52,7 +52,10 @@ public class BlockCube extends Group {
|
||||
public BlockCube(Integer weight, Double medianFee, Integer height, Integer txCount, Long timestamp, boolean confirmed) {
|
||||
getStyleClass().addAll("block-" + Network.getCanonical().getName(), "block-cube");
|
||||
this.confirmedProperty.set(confirmed);
|
||||
this.feeRatesSource.set(Config.get().getFeeRatesSource());
|
||||
|
||||
FeeRatesSource feeRatesSource = Config.get().getFeeRatesSource();
|
||||
feeRatesSource = (feeRatesSource == null ? FeeRatesSource.MEMPOOL_SPACE : feeRatesSource);
|
||||
this.feeRatesSource.set(feeRatesSource);
|
||||
|
||||
this.weightProperty.addListener((_, _, _) -> {
|
||||
if(front != null) {
|
||||
@@ -198,6 +201,8 @@ public class BlockCube extends Group {
|
||||
} else {
|
||||
feeRateIcon.getChildren().clear();
|
||||
}
|
||||
} else {
|
||||
feeRateIcon.getChildren().clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,10 @@ import javafx.util.Duration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.sparrowwallet.sparrow.AppServices.TARGET_BLOCKS_RANGE;
|
||||
import static com.sparrowwallet.sparrow.control.BlockCube.CUBE_SIZE;
|
||||
|
||||
public class RecentBlocksView extends Pane {
|
||||
@@ -48,12 +50,14 @@ public class RecentBlocksView extends Pane {
|
||||
}
|
||||
}));
|
||||
|
||||
updateFeeRatesSource(Config.get().getFeeRatesSource());
|
||||
FeeRatesSource feeRatesSource = Config.get().getFeeRatesSource();
|
||||
feeRatesSource = (feeRatesSource == null ? FeeRatesSource.MEMPOOL_SPACE : feeRatesSource);
|
||||
updateFeeRatesSource(feeRatesSource);
|
||||
Tooltip.install(this, tooltip);
|
||||
}
|
||||
|
||||
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()) {
|
||||
getCubes().getFirst().setFeeRatesSource(feeRatesSource);
|
||||
}
|
||||
@@ -104,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()) {
|
||||
return;
|
||||
}
|
||||
@@ -136,6 +140,14 @@ public class RecentBlocksView extends Pane {
|
||||
}
|
||||
}
|
||||
|
||||
public void updateFeeRate(Map<Integer, Double> targetBlockFeeRates) {
|
||||
int defaultTarget = TARGET_BLOCKS_RANGE.get((TARGET_BLOCKS_RANGE.size() / 2) - 1);
|
||||
if(targetBlockFeeRates.get(defaultTarget) != null) {
|
||||
Double defaultRate = targetBlockFeeRates.get(defaultTarget);
|
||||
updateFeeRate(defaultRate);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateFeeRate(Double currentFeeRate) {
|
||||
if(!getCubes().isEmpty()) {
|
||||
BlockCube firstCube = getCubes().getFirst();
|
||||
|
||||
@@ -6,12 +6,18 @@ import java.util.Map;
|
||||
|
||||
public class BlockSummaryEvent {
|
||||
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.nextBlockMedianFeeRate = nextBlockMedianFeeRate;
|
||||
}
|
||||
|
||||
public Map<Integer, BlockSummary> getBlockSummaryMap() {
|
||||
return blockSummaryMap;
|
||||
}
|
||||
|
||||
public Double getNextBlockMedianFeeRate() {
|
||||
return nextBlockMedianFeeRate;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,23 @@ import java.util.Set;
|
||||
|
||||
public class FeeRatesUpdatedEvent extends MempoolRateSizesUpdatedEvent {
|
||||
private final Map<Integer, Double> targetBlockFeeRates;
|
||||
private final Double nextBlockMedianFeeRate;
|
||||
|
||||
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);
|
||||
this.targetBlockFeeRates = targetBlockFeeRates;
|
||||
this.nextBlockMedianFeeRate = nextBlockMedianFeeRate;
|
||||
}
|
||||
|
||||
public Map<Integer, Double> getTargetBlockFeeRates() {
|
||||
return targetBlockFeeRates;
|
||||
}
|
||||
|
||||
public Double getNextBlockMedianFeeRate() {
|
||||
return nextBlockMedianFeeRate;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -936,6 +936,20 @@ public class ElectrumServer {
|
||||
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 {
|
||||
try {
|
||||
Map<Integer, Double> targetBlocksFeeRatesBtcKb = electrumServerRpc.getFeeEstimates(getTransport(), targetBlocks);
|
||||
@@ -1048,6 +1062,10 @@ public class ElectrumServer {
|
||||
List<BlockTransactionHash> recentTransactions = feeRatesSource.getRecentMempoolTransactions();
|
||||
Map<BlockTransactionHash, Transaction> setReferences = new HashMap<>();
|
||||
setReferences.put(recentTransactions.getFirst(), null);
|
||||
Random random = new Random();
|
||||
if(random.nextBoolean()) {
|
||||
setReferences.put(recentTransactions.get(random.nextInt(recentTransactions.size())), null);
|
||||
}
|
||||
Map<Sha256Hash, BlockTransaction> transactions = getTransactions(null, setReferences, Collections.emptyMap());
|
||||
return transactions.values().stream().filter(blxTx -> blxTx.getTransaction() != null).toList();
|
||||
} catch(Exception e) {
|
||||
@@ -1456,8 +1474,9 @@ public class ElectrumServer {
|
||||
if(elapsed > FEE_RATES_PERIOD) {
|
||||
Map<Integer, Double> blockTargetFeeRates = electrumServer.getFeeEstimates(AppServices.TARGET_BLOCKS_RANGE, false);
|
||||
Set<MempoolRateSize> mempoolRateSizes = electrumServer.getMempoolRateSizes();
|
||||
Double nextBlockMedianFeeRate = electrumServer.getNextBlockMedianFeeRate();
|
||||
feeRatesRetrievedAt = System.currentTimeMillis();
|
||||
return new FeeRatesUpdatedEvent(blockTargetFeeRates, mempoolRateSizes);
|
||||
return new FeeRatesUpdatedEvent(blockTargetFeeRates, mempoolRateSizes, nextBlockMedianFeeRate);
|
||||
}
|
||||
} else {
|
||||
closeConnection();
|
||||
@@ -1935,7 +1954,8 @@ public class ElectrumServer {
|
||||
protected FeeRatesUpdatedEvent call() throws ServerException {
|
||||
ElectrumServer electrumServer = new ElectrumServer();
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1985,7 +2005,11 @@ public class ElectrumServer {
|
||||
subscribeRecent(electrumServer);
|
||||
}
|
||||
|
||||
return new BlockSummaryEvent(blockSummaryMap);
|
||||
Double nextBlockMedianFeeRate = null;
|
||||
if(!isBlockstorm(totalBlocks)) {
|
||||
nextBlockMedianFeeRate = electrumServer.getNextBlockMedianFeeRate();
|
||||
}
|
||||
return new BlockSummaryEvent(blockSummaryMap, nextBlockMedianFeeRate);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2005,12 +2029,15 @@ public class ElectrumServer {
|
||||
Map<String, String> subscribeScriptHashes = new HashMap<>();
|
||||
List<BlockTransaction> recentTransactions = electrumServer.getRecentMempoolTransactions();
|
||||
for(BlockTransaction blkTx : recentTransactions) {
|
||||
for(int i = 0; i < blkTx.getTransaction().getOutputs().size() && subscribeScriptHashes.size() < 10; i++) {
|
||||
for(int i = 0; i < blkTx.getTransaction().getOutputs().size(); i++) {
|
||||
TransactionOutput txOutput = blkTx.getTransaction().getOutputs().get(i);
|
||||
String scriptHash = getScriptHash(txOutput);
|
||||
if(!subscribedScriptHashes.containsKey(scriptHash)) {
|
||||
subscribeScriptHashes.put("m/" + i, getScriptHash(txOutput));
|
||||
}
|
||||
if(Math.random() < 0.1d) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2023,14 +2050,24 @@ public class ElectrumServer {
|
||||
}
|
||||
}
|
||||
|
||||
if(!recentTransactions.isEmpty()) {
|
||||
broadcastRecent(electrumServer, recentTransactions);
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcastRecent(ElectrumServer electrumServer, List<BlockTransaction> recentTransactions) {
|
||||
ScheduledService<Void> broadcastService = new ScheduledService<>() {
|
||||
@Override
|
||||
protected Task<Void> createTask() {
|
||||
return new Task<>() {
|
||||
@Override
|
||||
protected Void call() throws Exception {
|
||||
for(BlockTransaction blkTx : recentTransactions) {
|
||||
electrumServer.broadcastTransaction(blkTx.getTransaction());
|
||||
if(!recentTransactions.isEmpty()) {
|
||||
Random random = new Random();
|
||||
if(random.nextBoolean()) {
|
||||
BlockTransaction blkTx = recentTransactions.get(random.nextInt(recentTransactions.size()));
|
||||
electrumServer.broadcastTransaction(blkTx.getTransaction());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,12 @@ public enum FeeRatesSource {
|
||||
return getThreeTierFeeRates(this, defaultblockTargetFeeRates, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getNextBlockMedianFeeRate() throws Exception {
|
||||
String url = getApiUrl() + "v1/fees/mempool-blocks";
|
||||
return requestNextBlockMedianFeeRate(this, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockSummary getBlockSummary(Sha256Hash blockId) throws Exception {
|
||||
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 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 {
|
||||
throw new UnsupportedOperationException(name + " does not support block summaries");
|
||||
}
|
||||
@@ -199,6 +209,30 @@ public enum FeeRatesSource {
|
||||
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 {
|
||||
if(log.isInfoEnabled()) {
|
||||
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) {
|
||||
public Double getMedianFee() {
|
||||
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));
|
||||
List<BlockSummary> blockSummaries = AppServices.getBlockSummaries().values().stream().sorted().toList();
|
||||
if(!blockSummaries.isEmpty()) {
|
||||
recentBlocksView.update(blockSummaries, AppServices.getDefaultFeeRate());
|
||||
recentBlocksView.update(blockSummaries, AppServices.getNextBlockMedianFeeRate());
|
||||
}
|
||||
|
||||
feeRatesSelectionProperty.addListener((_, oldValue, newValue) -> {
|
||||
@@ -1412,6 +1412,12 @@ public class SendController extends WalletFormController implements Initializabl
|
||||
}
|
||||
feeRange.updateTrackHighlight();
|
||||
|
||||
if(event.getNextBlockMedianFeeRate() != null) {
|
||||
recentBlocksView.updateFeeRate(event.getNextBlockMedianFeeRate());
|
||||
} else {
|
||||
recentBlocksView.updateFeeRate(event.getTargetBlockFeeRates());
|
||||
}
|
||||
|
||||
if(updateDefaultFeeRate) {
|
||||
if(getFeeRate() != null && Long.valueOf((long)getFallbackFeeRate()).equals(getFeeRate().longValue())) {
|
||||
setDefaultFeeRate();
|
||||
@@ -1434,7 +1440,7 @@ public class SendController extends WalletFormController implements Initializabl
|
||||
|
||||
@Subscribe
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user