mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-20 13:58:04 +01:00
Create utility endpoint for currency conversion
This commit is contained in:
committed by
Moxie Marlinspike
parent
47916ecb0f
commit
2dbab70c8c
@@ -0,0 +1,117 @@
|
||||
package org.whispersystems.textsecuregcm.currency;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.entities.CurrencyConversionEntity;
|
||||
import org.whispersystems.textsecuregcm.entities.CurrencyConversionEntityList;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import io.dropwizard.lifecycle.Managed;
|
||||
|
||||
public class CurrencyConversionManager implements Managed {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CurrencyConversionManager.class);
|
||||
|
||||
private static final long FIXER_INTERVAL = TimeUnit.HOURS.toMillis(2);
|
||||
private static final long FTX_INTERVAL = TimeUnit.MINUTES.toMillis(5);
|
||||
|
||||
private final FixerClient fixerClient;
|
||||
private final FtxClient ftxClient;
|
||||
private final List<String> currencies;
|
||||
|
||||
private AtomicReference<CurrencyConversionEntityList> cached = new AtomicReference<>(null);
|
||||
|
||||
private long fixerUpdatedTimestamp;
|
||||
private long ftxUpdatedTimestamp;
|
||||
|
||||
private Map<String, Double> cachedFixerValues;
|
||||
private Map<String, Double> cachedFtxValues;
|
||||
|
||||
public CurrencyConversionManager(FixerClient fixerClient, FtxClient ftxClient, List<String> currencies) {
|
||||
this.fixerClient = fixerClient;
|
||||
this.ftxClient = ftxClient;
|
||||
this.currencies = currencies;
|
||||
}
|
||||
|
||||
public Optional<CurrencyConversionEntityList> getCurrencyConversions() {
|
||||
return Optional.ofNullable(cached.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws Exception {
|
||||
new Thread(() -> {
|
||||
for (;;) {
|
||||
try {
|
||||
updateCacheIfNecessary();
|
||||
} catch (Throwable t) {
|
||||
logger.warn("Error updating currency conversions", t);
|
||||
}
|
||||
|
||||
Util.sleep(15000);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void updateCacheIfNecessary() throws IOException {
|
||||
if (System.currentTimeMillis() - fixerUpdatedTimestamp > FIXER_INTERVAL || cachedFixerValues == null) {
|
||||
this.cachedFixerValues = new HashMap<>(fixerClient.getConversionsForBase("USD"));
|
||||
this.fixerUpdatedTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
if (System.currentTimeMillis() - ftxUpdatedTimestamp > FTX_INTERVAL || cachedFtxValues == null) {
|
||||
Map<String, Double> cachedFtxValues = new HashMap<>();
|
||||
|
||||
for (String currency : currencies) {
|
||||
cachedFtxValues.put(currency, ftxClient.getSpotPrice(currency, "USD"));
|
||||
}
|
||||
|
||||
this.cachedFtxValues = cachedFtxValues;
|
||||
this.ftxUpdatedTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
List<CurrencyConversionEntity> entities = new LinkedList<>();
|
||||
|
||||
for (Map.Entry<String, Double> currency : cachedFtxValues.entrySet()) {
|
||||
double usdValue = currency.getValue();
|
||||
|
||||
Map<String, Double> values = new HashMap<>();
|
||||
values.put("USD", usdValue);
|
||||
|
||||
for (Map.Entry<String, Double> conversion : cachedFixerValues.entrySet()) {
|
||||
values.put(conversion.getKey(), conversion.getValue() * usdValue);
|
||||
}
|
||||
|
||||
entities.add(new CurrencyConversionEntity(currency.getKey(), values));
|
||||
}
|
||||
|
||||
|
||||
this.cached.set(new CurrencyConversionEntityList(entities, Math.min(fixerUpdatedTimestamp, ftxUpdatedTimestamp)));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setFixerUpdatedTimestamp(long timestamp) {
|
||||
this.fixerUpdatedTimestamp = timestamp;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setFtxUpdatedTimestamp(long timestamp) {
|
||||
this.ftxUpdatedTimestamp = timestamp;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.whispersystems.textsecuregcm.currency;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.Map;
|
||||
|
||||
public class FixerClient {
|
||||
|
||||
private final String apiKey;
|
||||
private final HttpClient client;
|
||||
|
||||
public FixerClient(HttpClient client, String apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public Map<String, Double> getConversionsForBase(String base) throws FixerException {
|
||||
try {
|
||||
URI uri = URI.create("https://data.fixer.io/api/latest?access_key=" + apiKey + "&base=" + base);
|
||||
|
||||
HttpResponse<String> response = client.send(HttpRequest.newBuilder()
|
||||
.GET()
|
||||
.uri(uri)
|
||||
.build(),
|
||||
HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
if (response.statusCode() < 200 || response.statusCode() >= 300) {
|
||||
throw new FixerException("Bad response: " + response.statusCode() + " " + response.toString());
|
||||
}
|
||||
|
||||
FixerResponse parsedResponse = SystemMapper.getMapper().readValue(response.body(), FixerResponse.class);
|
||||
|
||||
if (parsedResponse.success) return parsedResponse.rates;
|
||||
else throw new FixerException("Got failed response!");
|
||||
} catch (IOException | InterruptedException e) {
|
||||
throw new FixerException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class FixerResponse {
|
||||
|
||||
@JsonProperty
|
||||
private boolean success;
|
||||
|
||||
@JsonProperty
|
||||
private long timestamp;
|
||||
|
||||
@JsonProperty
|
||||
private String base;
|
||||
|
||||
@JsonProperty
|
||||
private String date;
|
||||
|
||||
@JsonProperty
|
||||
private Map<String, Double> rates;
|
||||
|
||||
}
|
||||
|
||||
public static class FixerException extends IOException {
|
||||
public FixerException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public FixerException(Exception exception) {
|
||||
super(exception);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package org.whispersystems.textsecuregcm.currency;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
|
||||
public class FtxClient {
|
||||
|
||||
private final HttpClient client;
|
||||
|
||||
public FtxClient(HttpClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public double getSpotPrice(String currency, String base) throws FtxException{
|
||||
try {
|
||||
URI uri = URI.create("https://ftx.com/api/markets/" + currency + "/" + base);
|
||||
|
||||
HttpResponse<String> response = client.send(HttpRequest.newBuilder()
|
||||
.GET()
|
||||
.uri(uri)
|
||||
.build(),
|
||||
HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
if (response.statusCode() < 200 || response.statusCode() >= 300) {
|
||||
throw new FtxException("Bad response: " + response.statusCode() + " " + response.toString());
|
||||
}
|
||||
|
||||
FtxResponse parsedResponse = SystemMapper.getMapper().readValue(response.body(), FtxResponse.class);
|
||||
|
||||
return parsedResponse.result.price;
|
||||
|
||||
} catch (IOException | InterruptedException e) {
|
||||
throw new FtxException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class FtxResponse {
|
||||
|
||||
@JsonProperty
|
||||
private FtxResult result;
|
||||
|
||||
}
|
||||
|
||||
private static class FtxResult {
|
||||
|
||||
@JsonProperty
|
||||
private double price;
|
||||
|
||||
}
|
||||
|
||||
public static class FtxException extends IOException {
|
||||
public FtxException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public FtxException(Exception exception) {
|
||||
super(exception);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user