mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-22 14:42:37 +01:00
Obtain minimumFee from mempool api in place of hardcoded value
This commit is contained in:
parent
0feb1079b8
commit
c33ac1b983
9 changed files with 53 additions and 25 deletions
|
@ -24,6 +24,7 @@ import bisq.core.provider.ProvidersRepository;
|
|||
import bisq.network.http.HttpClient;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.util.Tuple2;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
@ -58,8 +59,11 @@ public class FeeProvider extends HttpClientProvider {
|
|||
try {
|
||||
LinkedTreeMap<?, ?> dataMap = (LinkedTreeMap<?, ?>) linkedTreeMap.get("dataMap");
|
||||
Long btcTxFee = ((Double) dataMap.get("btcTxFee")).longValue();
|
||||
Long btcMinTxFee = dataMap.get("btcMinTxFee") != null ?
|
||||
((Double) dataMap.get("btcMinTxFee")).longValue() : Config.baseCurrencyNetwork().getDefaultMinFeePerVbyte();
|
||||
|
||||
map.put("BTC", btcTxFee);
|
||||
map.put("btcMinTxFee", btcMinTxFee);
|
||||
} catch (Throwable t) {
|
||||
log.error(t.toString());
|
||||
t.printStackTrace();
|
||||
|
|
|
@ -98,6 +98,7 @@ public class FeeService {
|
|||
private Map<String, Long> timeStampMap;
|
||||
@Getter
|
||||
private long lastRequest;
|
||||
@Getter
|
||||
private long minFeePerVByte;
|
||||
private long epochInSecondAtLastRequest;
|
||||
|
||||
|
@ -157,14 +158,15 @@ public class FeeService {
|
|||
epochInSecondAtLastRequest = timeStampMap.get("bitcoinFeesTs");
|
||||
final Map<String, Long> map = result.second;
|
||||
txFeePerVbyte = map.get("BTC");
|
||||
minFeePerVByte = map.get("btcMinTxFee");
|
||||
|
||||
if (txFeePerVbyte < minFeePerVByte) {
|
||||
log.warn("The delivered fee per vbyte is smaller than the min. default fee of 5 sat/vbyte");
|
||||
log.warn("The delivered fee of {} sat/vbyte is smaller than the min. default fee of {} sat/vbyte", txFeePerVbyte, minFeePerVByte);
|
||||
txFeePerVbyte = minFeePerVByte;
|
||||
}
|
||||
|
||||
feeUpdateCounter.set(feeUpdateCounter.get() + 1);
|
||||
log.info("BTC tx fee: txFeePerVbyte={}", txFeePerVbyte);
|
||||
log.info("BTC tx fee: txFeePerVbyte={} minFeePerVbyte={}", txFeePerVbyte, minFeePerVByte);
|
||||
if (resultHandler != null)
|
||||
resultHandler.run();
|
||||
});
|
||||
|
|
|
@ -29,6 +29,7 @@ import bisq.core.locale.GlobalSettings;
|
|||
import bisq.core.locale.TradeCurrency;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.PaymentAccountUtil;
|
||||
import bisq.core.provider.fee.FeeService;
|
||||
import bisq.core.setup.CoreNetworkCapabilities;
|
||||
|
||||
import bisq.network.p2p.network.BridgeAddressProvider;
|
||||
|
@ -165,6 +166,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||
|
||||
private final PersistenceManager<PreferencesPayload> persistenceManager;
|
||||
private final Config config;
|
||||
private final FeeService feeService;
|
||||
private final LocalBitcoinNode localBitcoinNode;
|
||||
private final String btcNodesFromOptions, referralIdFromOptions,
|
||||
rpcUserFromOptions, rpcPwFromOptions;
|
||||
|
@ -181,6 +183,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||
@Inject
|
||||
public Preferences(PersistenceManager<PreferencesPayload> persistenceManager,
|
||||
Config config,
|
||||
FeeService feeService,
|
||||
LocalBitcoinNode localBitcoinNode,
|
||||
@Named(Config.BTC_NODES) String btcNodesFromOptions,
|
||||
@Named(Config.REFERRAL_ID) String referralId,
|
||||
|
@ -191,6 +194,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||
|
||||
this.persistenceManager = persistenceManager;
|
||||
this.config = config;
|
||||
this.feeService = feeService;
|
||||
this.localBitcoinNode = localBitcoinNode;
|
||||
this.btcNodesFromOptions = btcNodesFromOptions;
|
||||
this.referralIdFromOptions = referralId;
|
||||
|
@ -907,7 +911,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||
|
||||
public long getWithdrawalTxFeeInVbytes() {
|
||||
return Math.max(prefPayload.getWithdrawalTxFeeInVbytes(),
|
||||
Config.baseCurrencyNetwork().getDefaultMinFeePerVbyte());
|
||||
feeService.getMinFeePerVByte());
|
||||
}
|
||||
|
||||
public boolean isDaoFullNode() {
|
||||
|
|
|
@ -294,7 +294,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
|
|||
String estimatedFee = String.valueOf(feeService.getTxFeePerVbyte().value);
|
||||
try {
|
||||
int withdrawalTxFeePerVbyte = Integer.parseInt(transactionFeeInputTextField.getText());
|
||||
final long minFeePerVbyte = Config.baseCurrencyNetwork().getDefaultMinFeePerVbyte();
|
||||
final long minFeePerVbyte = feeService.getMinFeePerVByte();
|
||||
if (withdrawalTxFeePerVbyte < minFeePerVbyte) {
|
||||
new Popup().warning(Res.get("setting.preferences.txFeeMin", minFeePerVbyte)).show();
|
||||
transactionFeeInputTextField.setText(estimatedFee);
|
||||
|
|
|
@ -24,11 +24,13 @@ public class FeeRate {
|
|||
|
||||
private final String currency;
|
||||
private final long price;
|
||||
private final long minimumFee;
|
||||
private final long timestamp;
|
||||
|
||||
public FeeRate(String currency, long price, long timestamp) {
|
||||
public FeeRate(String currency, long price, long minimumFee, long timestamp) {
|
||||
this.currency = currency;
|
||||
this.price = price;
|
||||
this.minimumFee = minimumFee;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
|
@ -40,6 +42,10 @@ public class FeeRate {
|
|||
return price;
|
||||
}
|
||||
|
||||
public long getMinimumFee() {
|
||||
return minimumFee;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ class FeeRateService {
|
|||
Map<String, Long> allFeeRates = new HashMap<>();
|
||||
|
||||
AtomicLong sumOfAllFeeRates = new AtomicLong();
|
||||
AtomicLong sumOfAllMinFeeRates = new AtomicLong();
|
||||
AtomicInteger amountOfFeeRates = new AtomicInteger();
|
||||
|
||||
// Process each provider, retrieve and store their fee rate
|
||||
|
@ -67,6 +68,7 @@ class FeeRateService {
|
|||
String currency = feeRate.getCurrency();
|
||||
if ("BTC".equals(currency)) {
|
||||
sumOfAllFeeRates.getAndAdd(feeRate.getPrice());
|
||||
sumOfAllMinFeeRates.getAndAdd(feeRate.getMinimumFee());
|
||||
amountOfFeeRates.getAndAdd(1);
|
||||
}
|
||||
});
|
||||
|
@ -75,10 +77,15 @@ class FeeRateService {
|
|||
long averageFeeRate = (amountOfFeeRates.intValue() > 0)
|
||||
? sumOfAllFeeRates.longValue() / amountOfFeeRates.intValue()
|
||||
: FeeRateProvider.MIN_FEE_RATE;
|
||||
long averageMinFeeRate = (amountOfFeeRates.intValue() > 0)
|
||||
? sumOfAllMinFeeRates.longValue() / amountOfFeeRates.intValue()
|
||||
: FeeRateProvider.MIN_FEE_RATE;
|
||||
|
||||
// Make sure the returned value is within the min-max range
|
||||
averageFeeRate = Math.max(averageFeeRate, FeeRateProvider.MIN_FEE_RATE);
|
||||
averageFeeRate = Math.min(averageFeeRate, FeeRateProvider.MAX_FEE_RATE);
|
||||
averageMinFeeRate = Math.max(averageMinFeeRate, FeeRateProvider.MIN_FEE_RATE);
|
||||
averageMinFeeRate = Math.min(averageMinFeeRate, FeeRateProvider.MAX_FEE_RATE);
|
||||
|
||||
// Prepare response: Add timestamp of now
|
||||
// Since this is an average, the timestamp is associated with when the moment in
|
||||
|
@ -87,6 +94,7 @@ class FeeRateService {
|
|||
|
||||
// Prepare response: Add the fee average
|
||||
allFeeRates.put("btcTxFee", averageFeeRate);
|
||||
allFeeRates.put("btcMinTxFee", averageMinFeeRate);
|
||||
|
||||
// Build response
|
||||
return new HashMap<>() {{
|
||||
|
|
|
@ -39,7 +39,7 @@ import java.time.Instant;
|
|||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* {@link FeeRateProvider} that interprets the Mempool API format to retrieve a mining
|
||||
|
@ -77,33 +77,37 @@ abstract class MempoolFeeRateProvider extends FeeRateProvider {
|
|||
protected FeeRate doGet() {
|
||||
// Default value is the minimum rate. If the connection to the fee estimate
|
||||
// provider fails, we fall back to this value.
|
||||
long estimatedFeeRate = MIN_FEE_RATE;
|
||||
try {
|
||||
estimatedFeeRate = getEstimatedFeeRate();
|
||||
return getEstimatedFeeRate();
|
||||
}
|
||||
catch (Exception e) {
|
||||
// Something happened with the connection
|
||||
log.error("Error retrieving bitcoin mining fee estimation: " + e.getMessage());
|
||||
}
|
||||
|
||||
return new FeeRate("BTC", estimatedFeeRate, Instant.now().getEpochSecond());
|
||||
return new FeeRate("BTC", MIN_FEE_RATE, MIN_FEE_RATE, Instant.now().getEpochSecond());
|
||||
}
|
||||
|
||||
private long getEstimatedFeeRate() {
|
||||
return getFeeRatePredictions()
|
||||
.filter(p -> p.getKey().equalsIgnoreCase("halfHourFee"))
|
||||
.map(Map.Entry::getValue)
|
||||
.findFirst()
|
||||
.map(r -> {
|
||||
log.info("Retrieved estimated mining fee of {} sat/vbyte from {}", r, getMempoolApiHostname());
|
||||
return r;
|
||||
})
|
||||
.map(r -> Math.max(r, MIN_FEE_RATE))
|
||||
.map(r -> Math.min(r, MAX_FEE_RATE))
|
||||
.orElse(MIN_FEE_RATE);
|
||||
private FeeRate getEstimatedFeeRate() {
|
||||
Set<Map.Entry<String, Long>> feeRatePredictions = getFeeRatePredictions();
|
||||
long estimatedFeeRate = feeRatePredictions.stream()
|
||||
.filter(p -> p.getKey().equalsIgnoreCase("halfHourFee"))
|
||||
.map(Map.Entry::getValue)
|
||||
.findFirst()
|
||||
.map(r -> Math.max(r, MIN_FEE_RATE))
|
||||
.map(r -> Math.min(r, MAX_FEE_RATE))
|
||||
.orElse(MIN_FEE_RATE);
|
||||
long minimumFee = feeRatePredictions.stream()
|
||||
.filter(p -> p.getKey().equalsIgnoreCase("minimumFee"))
|
||||
.map(Map.Entry::getValue)
|
||||
.findFirst()
|
||||
.map(r -> Math.multiplyExact(r, 2)) // multiply the minimumFee by 2 (per wiz)
|
||||
.orElse(MIN_FEE_RATE);
|
||||
log.info("Retrieved estimated mining fee of {} sat/vB and minimumFee of {} sat/vB from {}", estimatedFeeRate, minimumFee, getMempoolApiHostname());
|
||||
return new FeeRate("BTC", estimatedFeeRate, minimumFee, Instant.now().getEpochSecond());
|
||||
}
|
||||
|
||||
private Stream<Map.Entry<String, Long>> getFeeRatePredictions() {
|
||||
private Set<Map.Entry<String, Long>> getFeeRatePredictions() {
|
||||
return restTemplate.exchange(
|
||||
RequestEntity
|
||||
.get(UriComponentsBuilder
|
||||
|
@ -112,7 +116,7 @@ abstract class MempoolFeeRateProvider extends FeeRateProvider {
|
|||
.build().toUri())
|
||||
.build(),
|
||||
new ParameterizedTypeReference<Map<String, Long>>() { }
|
||||
).getBody().entrySet().stream();
|
||||
).getBody().entrySet();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -102,7 +102,7 @@ public class FeeRateServiceTest {
|
|||
assertNotEquals(0L, retrievedData.get("bitcoinFeesTs"));
|
||||
|
||||
Map<String, String> retrievedDataMap = (Map<String, String>) retrievedData.get("dataMap");
|
||||
assertEquals(1, retrievedDataMap.size());
|
||||
assertEquals(2, retrievedDataMap.size());
|
||||
assertEquals(expectedFeeRate, retrievedDataMap.get("btcTxFee"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ public class MempoolFeeRateProviderTest {
|
|||
MempoolFeeRateProvider dummyProvider = new MempoolFeeRateProvider.First(env) {
|
||||
@Override
|
||||
protected FeeRate doGet() {
|
||||
return new FeeRate("BTC", feeRate, Instant.now().getEpochSecond());
|
||||
return new FeeRate("BTC", feeRate, MIN_FEE_RATE, Instant.now().getEpochSecond());
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue