Make tx fee dynamic by base currency

This commit is contained in:
Manfred Karrer 2017-06-13 07:35:35 +02:00
parent 5a0d493d02
commit 9aba9e607a
8 changed files with 71 additions and 107 deletions

View file

@ -1,14 +0,0 @@
package io.bisq.core.provider.fee;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FeeData {
private static final Logger log = LoggerFactory.getLogger(FeeData.class);
public final long txFeePerByte;
public FeeData(long txFeePerByte) {
this.txFeePerByte = txFeePerByte;
}
}

View file

@ -21,7 +21,7 @@ public class FeeProvider extends HttpClientProvider {
super(httpClient, baseUrl, false);
}
public Tuple2<Map<String, Long>, FeeData> getFees() throws IOException {
public Tuple2<Map<String, Long>, Map<String, Long>> getFees() throws IOException {
String json = httpClient.requestWithGET("getFees", "User-Agent", "bisq/" + Version.VERSION + ", uid:" + httpClient.getUid());
//noinspection unchecked
LinkedTreeMap<String, Object> linkedTreeMap = new Gson().fromJson(json, LinkedTreeMap.class);
@ -29,9 +29,15 @@ public class FeeProvider extends HttpClientProvider {
tsMap.put("bitcoinFeesTs", ((Double) linkedTreeMap.get("bitcoinFeesTs")).longValue());
//noinspection unchecked
LinkedTreeMap<String, Double> dataMap = (LinkedTreeMap<String, Double>) linkedTreeMap.get("data");
FeeData feeData = new FeeData(dataMap.get("txFee").longValue());
return new Tuple2<>(tsMap, feeData);
LinkedTreeMap<String, Double> dataMap = (LinkedTreeMap<String, Double>) linkedTreeMap.get("dataMap");
Long btcTxFee = dataMap.get("btcTxFee").longValue();
Long ltcTxFee = dataMap.get("ltcTxFee").longValue();
Long dogeTxFee = dataMap.get("dogeTxFee").longValue();
Map<String, Long> map = new HashMap<>();
map.put("BTC", btcTxFee);
map.put("LTC", ltcTxFee);
map.put("DOGE", dogeTxFee);
return new Tuple2<>(tsMap, map);
}
@Override

View file

@ -17,15 +17,15 @@ public class FeeRequest {
public FeeRequest() {
}
public SettableFuture<Tuple2<Map<String, Long>, FeeData>> getFees(FeeProvider provider) {
final SettableFuture<Tuple2<Map<String, Long>, FeeData>> resultFuture = SettableFuture.create();
ListenableFuture<Tuple2<Map<String, Long>, FeeData>> future = executorService.submit(() -> {
public SettableFuture<Tuple2<Map<String, Long>, Map<String, Long>>> getFees(FeeProvider provider) {
final SettableFuture<Tuple2<Map<String, Long>, Map<String, Long>>> resultFuture = SettableFuture.create();
ListenableFuture<Tuple2<Map<String, Long>, Map<String, Long>>> future = executorService.submit(() -> {
Thread.currentThread().setName("FeeRequest-" + provider.toString());
return provider.getFees();
});
Futures.addCallback(future, new FutureCallback<Tuple2<Map<String, Long>, FeeData>>() {
public void onSuccess(Tuple2<Map<String, Long>, FeeData> feeData) {
Futures.addCallback(future, new FutureCallback<Tuple2<Map<String, Long>, Map<String, Long>>>() {
public void onSuccess(Tuple2<Map<String, Long>, Map<String, Long>> feeData) {
log.debug("Received feeData of {}\nfrom provider {}", feeData, provider);
resultFuture.set(feeData);
}

View file

@ -24,6 +24,7 @@ import com.google.inject.Inject;
import io.bisq.common.UserThread;
import io.bisq.common.handlers.FaultHandler;
import io.bisq.common.util.Tuple2;
import io.bisq.core.app.BisqEnvironment;
import io.bisq.core.provider.ProvidersRepository;
import io.bisq.network.http.HttpClient;
import org.bitcoinj.core.Coin;
@ -40,20 +41,11 @@ import static com.google.common.base.Preconditions.checkNotNull;
public class FeeService {
private static final Logger log = LoggerFactory.getLogger(FeeService.class);
// TODO check min fee for LTC
// https://litecoin.info/Transaction_fees
//0.001 (LTC)/kb -> 0.00100000 sat/kb -> 100 sat/byte
public static final long MIN_TX_FEE = 100; // satoshi/byte // 0.01 USD at LTC price 30 USD for a 300 byte tx
public static final long MAX_TX_FEE = 400;
public static final long DEFAULT_TX_FEE = 100;
/*
BTC
public static final long MIN_TX_FEE = 40; // satoshi/byte
public static final long MAX_TX_FEE = 1000;
public static final long DEFAULT_TX_FEE = 150;
*/
public static final long LTC_DEFAULT_TX_FEE = 100;
public static final long BTC_DEFAULT_TX_FEE = 150;
public static final long DOGE_DEFAULT_TX_FEE = 100;
// Dust limit for LTC is 100 000 sat
// https://litecoin.info/Transaction_fees
@ -73,8 +65,8 @@ public class FeeService {
public static final long MIN_PAUSE_BETWEEN_REQUESTS_IN_MIN = 10;
private final FeeProvider feeProvider;
@Nullable
private FeeData feeData;
private final String baseCurrencyCode;
private long txFeePerByte;
private Map<String, Long> timeStampMap;
private long epochInSecondAtLastRequest;
private long lastRequest;
@ -84,8 +76,23 @@ public class FeeService {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public FeeService(HttpClient httpClient, ProvidersRepository providersRepository) {
this.feeProvider = new FeeProvider(httpClient, providersRepository.getBaseUrl());
public FeeService(HttpClient httpClient, ProvidersRepository providersRepository, BisqEnvironment bisqEnvironment) {
baseCurrencyCode = bisqEnvironment.getBaseCurrencyNetwork().getCurrencyCode();
feeProvider = new FeeProvider(httpClient, providersRepository.getBaseUrl());
switch (baseCurrencyCode) {
case "BTC":
txFeePerByte = BTC_DEFAULT_TX_FEE;
break;
case "LTC":
txFeePerByte = LTC_DEFAULT_TX_FEE;
break;
case "DOGE":
txFeePerByte = DOGE_DEFAULT_TX_FEE;
break;
default:
throw new RuntimeException("baseCurrencyCode not defined. baseCurrencyCode=" + baseCurrencyCode);
}
}
public void onAllServicesInitialized() {
@ -94,19 +101,20 @@ public class FeeService {
public void requestFees(@Nullable Runnable resultHandler, @Nullable FaultHandler faultHandler) {
long now = Instant.now().getEpochSecond();
if (feeData == null || now - lastRequest > MIN_PAUSE_BETWEEN_REQUESTS_IN_MIN * 60) {
if (now - lastRequest > MIN_PAUSE_BETWEEN_REQUESTS_IN_MIN * 60) {
lastRequest = now;
FeeRequest feeRequest = new FeeRequest();
SettableFuture<Tuple2<Map<String, Long>, FeeData>> future = feeRequest.getFees(feeProvider);
Futures.addCallback(future, new FutureCallback<Tuple2<Map<String, Long>, FeeData>>() {
SettableFuture<Tuple2<Map<String, Long>, Map<String, Long>>> future = feeRequest.getFees(feeProvider);
Futures.addCallback(future, new FutureCallback<Tuple2<Map<String, Long>, Map<String, Long>>>() {
@Override
public void onSuccess(@Nullable Tuple2<Map<String, Long>, FeeData> result) {
public void onSuccess(@Nullable Tuple2<Map<String, Long>, Map<String, Long>> result) {
UserThread.execute(() -> {
checkNotNull(result, "Result must not be null at getFees");
timeStampMap = result.first;
epochInSecondAtLastRequest = timeStampMap.get("bitcoinFeesTs");
feeData = result.second;
log.info("Tx fee: txFeePerByte=" + feeData.txFeePerByte);
final Map<String, Long> map = result.second;
txFeePerByte = map.get(baseCurrencyCode);
log.info("Tx fee: txFeePerByte={} for currency {}", txFeePerByte, baseCurrencyCode);
if (resultHandler != null)
resultHandler.run();
});
@ -133,10 +141,7 @@ public class FeeService {
}
public Coin getTxFeePerByte() {
if (feeData != null)
return Coin.valueOf(feeData.txFeePerByte);
else
return Coin.valueOf(DEFAULT_TX_FEE);
return Coin.valueOf(txFeePerByte);
}
public static Coin getMakerFeePerBtc(boolean currencyForMakerFeeBtc) {

View file

@ -19,8 +19,7 @@ package io.bisq.provider.fee;
import io.bisq.common.util.Utilities;
import io.bisq.core.provider.fee.FeeService;
import io.bisq.provider.fee.providers.FeesProvider;
import io.bisq.provider.fee.providers.LtcFeesProvider;
import io.bisq.provider.fee.providers.BtcFeesProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -38,17 +37,24 @@ public class FeeRequestService {
private static final long INTERVAL_BTC_FEES_MS = 600_000; // 10 min
public static final long BTC_MIN_TX_FEE = 40; // satoshi/byte
public static final long BTC_MAX_TX_FEE = 2000;
private final Timer timerBitcoinFeesLocal = new Timer();
private final FeesProvider feesProvider;
private final Map<String, Long> allFeesMap = new ConcurrentHashMap<>();
private final BtcFeesProvider btcFeesProvider;
private final Map<String, Long> dataMap = new ConcurrentHashMap<>();
private long bitcoinFeesTs;
private String json;
public FeeRequestService() throws IOException {
// feesProvider = new BtcFeesProvider();
feesProvider = new LtcFeesProvider();
btcFeesProvider = new BtcFeesProvider();
// For now we don't need a fee estimation for LTC so we set it fixed, but we keep it in the provider to
// be flexible if fee pressure grows on LTC
dataMap.put("ltcTxFee", FeeService.LTC_DEFAULT_TX_FEE);
dataMap.put("dogeTxFee", FeeService.DOGE_DEFAULT_TX_FEE);
writeToJson();
startRequests();
}
@ -72,15 +78,15 @@ public class FeeRequestService {
private void requestBitcoinFees() throws IOException {
long ts = System.currentTimeMillis();
long result = feesProvider.getFee();
long btcFee = btcFeesProvider.getFee();
log.info("requestBitcoinFees took {} ms.", (System.currentTimeMillis() - ts));
if (result < FeeService.MIN_TX_FEE) {
log.warn("Response for fee is lower as min fee. Fee=" + result);
} else if (result > FeeService.MAX_TX_FEE) {
log.warn("Response for fee is larger as max fee. Fee=" + result);
if (btcFee < FeeRequestService.BTC_MIN_TX_FEE) {
log.warn("Response for fee is lower as min fee. Fee=" + btcFee);
} else if (btcFee > FeeRequestService.BTC_MAX_TX_FEE) {
log.warn("Response for fee is larger as max fee. Fee=" + btcFee);
} else {
bitcoinFeesTs = Instant.now().getEpochSecond();
allFeesMap.put("txFee", result);
dataMap.put("btcTxFee", btcFee);
writeToJson();
}
}
@ -88,7 +94,7 @@ public class FeeRequestService {
private void writeToJson() {
Map<String, Object> map = new HashMap<>();
map.put("bitcoinFeesTs", bitcoinFeesTs);
map.put("data", allFeesMap);
map.put("dataMap", dataMap);
json = Utilities.objectToJson(map);
}

View file

@ -2,8 +2,8 @@ package io.bisq.provider.fee.providers;
import com.google.gson.Gson;
import com.google.gson.internal.LinkedTreeMap;
import io.bisq.core.provider.fee.FeeService;
import io.bisq.network.http.HttpClient;
import io.bisq.provider.fee.FeeRequestService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -12,7 +12,7 @@ import java.util.HashMap;
import java.util.Map;
//TODO use protobuffer instead of json
public class BtcFeesProvider implements FeesProvider {
public class BtcFeesProvider {
private static final Logger log = LoggerFactory.getLogger(BtcFeesProvider.class);
private final HttpClient httpClient;
@ -29,13 +29,13 @@ public class BtcFeesProvider implements FeesProvider {
LinkedTreeMap<String, Double> treeMap = new Gson().fromJson(response, LinkedTreeMap.class);
treeMap.entrySet().stream().forEach(e -> map.put(e.getKey(), e.getValue().longValue()));
if (map.get("fastestFee") < FeeService.MAX_TX_FEE)
if (map.get("fastestFee") < FeeRequestService.BTC_MAX_TX_FEE)
return map.get("fastestFee");
else if (map.get("halfHourFee") < FeeService.MAX_TX_FEE)
else if (map.get("halfHourFee") < FeeRequestService.BTC_MAX_TX_FEE)
return map.get("halfHourFee");
else if (map.get("hourFee") < FeeService.MAX_TX_FEE)
else if (map.get("hourFee") < FeeRequestService.BTC_MAX_TX_FEE)
return map.get("hourFee");
else
return FeeService.MAX_TX_FEE;
return FeeRequestService.BTC_MAX_TX_FEE;
}
}

View file

@ -1,24 +0,0 @@
/*
* This file is part of bisq.
*
* bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bisq.provider.fee.providers;
import java.io.IOException;
public interface FeesProvider {
Long getFee() throws IOException;
}

View file

@ -1,15 +0,0 @@
package io.bisq.provider.fee.providers;
import io.bisq.core.provider.fee.FeeService;
import java.io.IOException;
public class LtcFeesProvider implements FeesProvider {
public LtcFeesProvider() {
}
public Long getFee() throws IOException {
return FeeService.DEFAULT_TX_FEE;
}
}