Obtain minimumFee from mempool api in place of hardcoded value

This commit is contained in:
jmacxx 2021-02-24 22:15:13 -06:00
parent 0feb1079b8
commit c33ac1b983
No known key found for this signature in database
GPG key ID: 155297BABFE94A1B
9 changed files with 53 additions and 25 deletions

View file

@ -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();

View file

@ -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();
});

View file

@ -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() {

View file

@ -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);

View file

@ -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;
}

View file

@ -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<>() {{

View file

@ -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();
}
/**

View file

@ -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"));
}
}

View file

@ -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());
}
};