Skip to content
This repository has been archived by the owner on Jul 12, 2022. It is now read-only.

Commit

Permalink
Sync with exchange 'provider' module @ v0.6.4 (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
cbeams committed Jan 23, 2018
1 parent 0bfe4ef commit 2847686
Show file tree
Hide file tree
Showing 10 changed files with 268 additions and 260 deletions.
120 changes: 86 additions & 34 deletions src/main/java/io/bisq/provider/ProviderMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,62 +17,114 @@

package io.bisq.provider;

import ch.qos.logback.classic.Level;
import io.bisq.common.app.Log;
import io.bisq.common.app.Version;
import io.bisq.common.util.Utilities;
import io.bisq.network.http.HttpException;
import io.bisq.provider.fee.FeeRequestService;
import io.bisq.provider.fee.providers.BtcFeesProvider;
import io.bisq.provider.price.PriceRequestService;

import io.bisq.common.app.Log;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.spec.InvalidKeySpecException;
import java.util.Locale;
import java.util.concurrent.TimeUnit;

import static spark.Spark.get;
import static spark.Spark.port;

public class ProviderMain {

private static final Logger log = LoggerFactory.getLogger(ProviderMain.class);

public static void main(String[] args) throws Exception {
Log.setup(System.getProperty("user.home") + File.separator + "provider");
static {
// Need to set default locale initially otherwise we get problems at non-english OS
Locale.setDefault(new Locale("en", Locale.getDefault().getCountry()));

Utilities.removeCryptographyRestrictions();
}

public static void main(String[] args) throws IOException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, InvalidKeyException, HttpException {
final String logPath = System.getProperty("user.home") + File.separator + "provider";
Log.setup(logPath);
Log.setLevel(Level.INFO);
log.info("Log files under: " + logPath);
log.info("ProviderVersion.VERSION: " + ProviderVersion.VERSION);
log.info("Bisq exchange Version{" +
"VERSION=" + Version.VERSION +
", P2P_NETWORK_VERSION=" + Version.P2P_NETWORK_VERSION +
", LOCAL_DB_VERSION=" + Version.LOCAL_DB_VERSION +
", TRADE_PROTOCOL_VERSION=" + Version.TRADE_PROTOCOL_VERSION +
", BASE_CURRENCY_NETWORK=NOT SET" +
", getP2PNetworkId()=NOT SET" +
'}');
Utilities.printSysInfo();

String bitcoinAveragePrivKey = null;
String bitcoinAveragePubKey = null;
int capacity = BtcFeesProvider.CAPACITY;
int maxBlocks = BtcFeesProvider.MAX_BLOCKS;
long requestIntervalInMs = TimeUnit.MINUTES.toMillis(FeeRequestService.REQUEST_INTERVAL_MIN);

// extract command line arguments
if (args.length < 2) {
log.error("You need to provide the BitcoinAverage API keys. Private key as first argument, public key as second argument.");
System.exit(1);
}
if (args.length >= 2) {
bitcoinAveragePrivKey = args[0];
bitcoinAveragePubKey = args[1];
}
if (args.length >= 4) {
capacity = Integer.valueOf(args[2]);
maxBlocks = Integer.valueOf(args[3]);
}
if (args.length >= 5) {
requestIntervalInMs = TimeUnit.MINUTES.toMillis(Long.valueOf(args[4]));
}

port(System.getenv("PORT") != null ? Integer.valueOf(System.getenv("PORT")) : 8080);
port(8080);

handleGetAllMarketPrices();
handleGetFees();
handleGetAllMarketPrices(bitcoinAveragePrivKey, bitcoinAveragePubKey);
handleGetFees(capacity, maxBlocks, requestIntervalInMs);
handleGetVersion();
handleGetParams(capacity, maxBlocks, requestIntervalInMs);
}

private static void handleGetAllMarketPrices() throws IOException, NoSuchAlgorithmException, InvalidKeyException {
if (System.getenv("BITCOIN_AVG_PRIVKEY") != null && System.getenv("BITCOIN_AVG_PUBKEY") != null) {
String bitcoinAveragePrivKey = System.getenv("BITCOIN_AVG_PRIVKEY");
String bitcoinAveragePubKey = System.getenv("BITCOIN_AVG_PUBKEY");

PriceRequestService priceRequestService =
new PriceRequestService(bitcoinAveragePrivKey, bitcoinAveragePubKey);

get("/getAllMarketPrices", (req, res) -> {
log.info("Incoming getAllMarketPrices request from: " + req.userAgent());
return priceRequestService.getJson();
});
} else {
throw new IllegalArgumentException("You need to provide the BitcoinAverage API keys. " +
"Private key as BITCOIN_AVG_PRIVKEY environment variable, " +
"public key as BITCOIN_AVG_PUBKEY environment variable");
}
private static void handleGetAllMarketPrices(String bitcoinAveragePrivKey, String bitcoinAveragePubKey)
throws IOException, NoSuchAlgorithmException, InvalidKeyException {
PriceRequestService priceRequestService = new PriceRequestService(bitcoinAveragePrivKey, bitcoinAveragePubKey);
get("/getAllMarketPrices", (req, res) -> {
log.info("Incoming getAllMarketPrices request from: " + req.userAgent());
return priceRequestService.getJson();
});
}

private static void handleGetFees() throws IOException {
FeeRequestService feeRequestService = new FeeRequestService();
private static void handleGetFees(int capacity, int maxBlocks, long requestIntervalInMs) throws IOException {
FeeRequestService feeRequestService = new FeeRequestService(capacity, maxBlocks, requestIntervalInMs);
get("/getFees", (req, res) -> {
log.info("Incoming getFees request from: " + req.userAgent());
return feeRequestService.getJson();
});
}

private static void handleGetVersion() throws IOException {
get("/getVersion", (req, res) -> {
log.info("Incoming getVersion request from: " + req.userAgent());
return ProviderVersion.VERSION;
});
}

private static void handleGetParams(int capacity, int maxBlocks, long requestIntervalInMs) throws IOException {
get("/getParams", (req, res) -> {
log.info("Incoming getParams request from: " + req.userAgent());
return capacity + ";" + maxBlocks + ";" + requestIntervalInMs;
});
}
}
22 changes: 22 additions & 0 deletions src/main/java/io/bisq/provider/ProviderVersion.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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;

public class ProviderVersion {
public static final String VERSION = "0.6.4";
}
40 changes: 17 additions & 23 deletions src/main/java/io/bisq/provider/fee/FeeRequestService.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,53 +17,47 @@

package io.bisq.provider.fee;

import io.bisq.provider.fee.providers.BtcFeesProvider;

import io.bisq.common.util.Utilities;

import java.time.Instant;
import io.bisq.core.provider.fee.FeeService;
import io.bisq.provider.fee.providers.BtcFeesProvider;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;

import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Slf4j
public class FeeRequestService {
public static int REQUEST_INTERVAL_MIN = 5;

private static final Logger log = LoggerFactory.getLogger(FeeRequestService.class);

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;
public static final long BTC_MIN_TX_FEE = 10; // satoshi/byte
public static final long BTC_MAX_TX_FEE = 1000;

private final Timer timerBitcoinFeesLocal = new Timer();

private final BtcFeesProvider btcFeesProvider;
private final Map<String, Long> dataMap = new ConcurrentHashMap<>();

private long bitcoinFeesTs;
private String json;

public FeeRequestService() throws IOException {
btcFeesProvider = new BtcFeesProvider();
public FeeRequestService(int capacity, int maxBlocks, long requestIntervalInMs) throws IOException {
btcFeesProvider = new BtcFeesProvider(capacity, maxBlocks);

// 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", 500L /*FeeService.LTC_DEFAULT_TX_FEE*/);
dataMap.put("dogeTxFee", 5_000_000L /*FeeService.DOGE_DEFAULT_TX_FEE*/);
dataMap.put("dashTxFee", 50L /*FeeService.DASH_DEFAULT_TX_FEE*/);
dataMap.put("ltcTxFee", FeeService.LTC_DEFAULT_TX_FEE);
dataMap.put("dogeTxFee", FeeService.DOGE_DEFAULT_TX_FEE);
dataMap.put("dashTxFee", FeeService.DASH_DEFAULT_TX_FEE);

writeToJson();
startRequests();
startRequests(requestIntervalInMs);
}

private void startRequests() throws IOException {
private void startRequests(long requestIntervalInMs) throws IOException {
timerBitcoinFeesLocal.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
Expand All @@ -74,7 +68,7 @@ public void run() {
e.printStackTrace();
}
}
}, INTERVAL_BTC_FEES_MS, INTERVAL_BTC_FEES_MS);
}, requestIntervalInMs, requestIntervalInMs);


requestBitcoinFees();
Expand Down
83 changes: 40 additions & 43 deletions src/main/java/io/bisq/provider/fee/providers/BtcFeesProvider.java
Original file line number Diff line number Diff line change
@@ -1,62 +1,46 @@
/*
* 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 io.bisq.provider.fee.FeeRequestService;

import io.bisq.network.http.HttpClient;

import io.bisq.common.util.MathUtils;

import com.google.gson.Gson;
import com.google.gson.internal.LinkedTreeMap;
import io.bisq.common.util.MathUtils;
import io.bisq.network.http.HttpClient;
import io.bisq.provider.fee.FeeRequestService;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;

import java.util.ArrayList;
import java.util.LinkedList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

//TODO consider alternative https://www.bitgo.com/api/v1/tx/fee?numBlocks=3
@Slf4j
public class BtcFeesProvider {

private static final Logger log = LoggerFactory.getLogger(BtcFeesProvider.class);
public static int CAPACITY = 4; // if we request each 5 min. we take average of last 20 min.
public static int MAX_BLOCKS = 10;

private final HttpClient httpClient;

public BtcFeesProvider() {
// we previously used https://bitcoinfees.21.co/api/v1/fees/recommended but fees were way too high
// see also: https://estimatefee.com/n/2
this.httpClient = new HttpClient("https://bitcoinfees.21.co/api/v1/fees/");
LinkedList<Long> fees = new LinkedList<>();
private final int capacity;
private final int maxBlocks;

// other: https://estimatefee.com/n/2
public BtcFeesProvider(int capacity, int maxBlocks) {
this.capacity = capacity;
this.maxBlocks = maxBlocks;
this.httpClient = new HttpClient("https://bitcoinfees.earn.com/api/v1/fees/");
}

public Long getFee() throws IOException {
// prev. used: https://bitcoinfees.earn.com/api/v1/fees/recommended
// but was way too high

// https://bitcoinfees.earn.com/api/v1/fees/list
String response = httpClient.requestWithGET("list", "User-Agent", "");
log.info("Get recommended fee response: " + response);

@SuppressWarnings("unchecked")
LinkedTreeMap<String, ArrayList<LinkedTreeMap<String, Double>>> treeMap =
new Gson().fromJson(response, LinkedTreeMap.class);

LinkedTreeMap<String, ArrayList<LinkedTreeMap<String, Double>>> treeMap = new Gson().fromJson(response, LinkedTreeMap.class);
final long[] fee = new long[1];

// we want a fee that gets us in within 10 blocks max
int maxBlocks = 10;
// we want a fee which is at least in 20 blocks in (21.co estimation seem to be way too high, so we get
// prob much faster in
treeMap.entrySet().stream()
.flatMap(e -> e.getValue().stream())
.forEach(e -> {
Expand All @@ -65,7 +49,20 @@ public Long getFee() throws IOException {
fee[0] = MathUtils.roundDoubleToLong(e.get("maxFee"));
});
fee[0] = Math.min(Math.max(fee[0], FeeRequestService.BTC_MIN_TX_FEE), FeeRequestService.BTC_MAX_TX_FEE);
log.info("fee " + fee[0]);
return fee[0];

return getAverage(fee[0]);
}

// We take the average of the last 12 calls (every 5 minute) so we smooth extreme values.
// We observed very radical jumps in the fee estimations, so that should help to avoid that.
long getAverage(long newFee) {
log.info("new fee " + newFee);
fees.add(newFee);
long average = ((Double) fees.stream().mapToDouble(e -> e).average().getAsDouble()).longValue();
log.info("average of last {} calls: {}", fees.size(), average);
if (fees.size() == capacity)
fees.removeFirst();

return average;
}
}
Loading

0 comments on commit 2847686

Please sign in to comment.