Merge pricenode repository at 85df033

This commit is contained in:
Christoph Atteneder 2018-09-14 11:45:35 +02:00
parent ecef18a437
commit 9a7b761549
No known key found for this signature in database
GPG key ID: CD5DC1C529CDFD3B
41 changed files with 1687 additions and 0 deletions

1
pricenode/Procfile Normal file
View file

@ -0,0 +1 @@
web: if [ "$HIDDEN" == true ]; then ./tor/bin/run_tor java -jar -Dserver.port=$PORT build/libs/bisq-pricenode.jar; else java -jar -Dserver.port=$PORT build/libs/bisq-pricenode.jar; fi

View file

@ -0,0 +1,43 @@
Deploy on Heroku
--------
Run the following commands:
heroku create
heroku buildpacks:add heroku/gradle
heroku config:set BITCOIN_AVG_PUBKEY=[your pubkey] BITCOIN_AVG_PRIVKEY=[your privkey]
git push heroku master
curl https://your-app-123456.herokuapp.com/getAllMarketPrices
To register the node as a Tor hidden service, first install the Heroku Tor buildpack:
heroku buildpacks:add https://github.com/cbeams/heroku-buildpack-tor.git
git push heroku master
> NOTE: this deployment will take a while, because the new buildpack must download and build Tor from source.
Next, generate your Tor hidden service private key and .onion address:
heroku run bash
./tor/bin/tor -f torrc
When the process reports that it is "100% bootstrapped", kill it, then copy the generated private key and .onion hostname values:
cat build/tor-hidden-service/hostname
cat build/tor-hidden-service/private_key
exit
> IMPORTANT: Save the private key value in a secure location so that this node can be re-created elsewhere with the same .onion address in the future if necessary.
Now configure the hostname and private key values as environment variables for your Heroku app:
heroku config:set HIDDEN=true HIDDEN_DOT_ONION=[your .onion] HIDDEN_PRIVATE_KEY="[your tor privkey]"
git push heroku master
When the application finishes restarting, you should still be able to access it via the clearnet, e.g. with:
curl https://your-app-123456.herokuapp.com/getAllMarketPrices
And via your Tor Browser at:
http://$YOUR_ONION/getAllMarketPrices

20
pricenode/TODO.md Normal file
View file

@ -0,0 +1,20 @@
# Refactorings
The list of stuff remaining to complete the PR at https://github.com/bisq-network/pricenode/pull/7
- Document provider implementations w/ links to API docs, etc
- Add integration tests
- Document / discuss how operators should (ideally) operate their pricenodes on a push-to-deploy model, e.g. how it's done on Heroku
## Non-refactorings
Most or all of these will become individual issues / PRs. Just capturing them here for convenience now. Not all may make sense.
- Deprecate existing get* endpoints (e.g. /getAllMarketPrices) in favor of '/exchange-rates', '/fee-estimate;
- Eliminate dependency on bisq-core (only real need now is CurrencyUtil for list of supported coins)
- Remove command line args for fee estimation params; hard-code these values and update them via commits, not via one-off changes by each operator
- Remove 'getParams' in favor of Boot actuator endpoint
- Update bisq-network/exchange to refer to 'provider' as 'pricenode'
- Invert the dependency arrangement. Move 'ProviderRepository' et al from bisq-network/exchange here into
bisq-network/pricenode and have bisq-network/exchange depend on it as a client lib
- Save bandwidth and be idiomatic by not pretty-printing json returned from /getAllMarketPrices et al

34
pricenode/build.gradle Normal file
View file

@ -0,0 +1,34 @@
plugins {
id "java"
id "org.springframework.boot" version "1.5.10.RELEASE"
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
version = file("src/main/resources/version.txt").text
jar.manifest.attributes(
"Implementation-Title": rootProject.name,
"Implementation-Version": version)
jar.archiveName "${rootProject.name}.jar"
repositories {
jcenter()
maven { url "https://jitpack.io" }
maven { url "https://raw.githubusercontent.com/JesusMcCloud/tor-binary/master/release/" }
}
dependencies {
compile project(":core")
compile("org.knowm.xchange:xchange-bitcoinaverage:4.3.3")
compile("org.knowm.xchange:xchange-coinmarketcap:4.3.3")
compile("org.knowm.xchange:xchange-poloniex:4.3.3")
compile("org.springframework.boot:spring-boot-starter-web:1.5.10.RELEASE")
compile("org.springframework.boot:spring-boot-starter-actuator")
}
task stage {
dependsOn assemble
}

View file

@ -0,0 +1,26 @@
###
# The directory of the Dockerfile should contain your 'hostname' and 'private_key' files.
# In the docker-compose.yml file you can pass the ONION_ADDRESS referenced below.
###
# pull base image
FROM openjdk:8-jdk
RUN apt-get update && apt-get install -y --no-install-recommends \
vim \
tor \
fakeroot \
sudo \
openjfx && rm -rf /var/lib/apt/lists/*
RUN git clone https://github.com/bisq-network/pricenode.git
WORKDIR /pricenode/
RUN ./gradlew assemble
COPY loop.sh start_node.sh start_tor.sh ./
COPY hostname private_key /var/lib/tor/
COPY torrc /etc/tor/
RUN chmod +x *.sh && chown debian-tor:debian-tor /etc/tor/torrc /var/lib/tor/hostname /var/lib/tor/private_key
CMD ./start_tor.sh && ./start_node.sh
#CMD tail -f /dev/null

View file

@ -0,0 +1,43 @@
Needed information to start a pricenode
==
Copy to this directory:
--
* a tor `hostname` file, containing your onion address
* a tor `private_key` file, containing the private key for your tor hidden service
Edit docker-compose.yml:
--
* fill in your public and private api keys (needs a btcaverage developer subscription)
Needed software to start a pricenode
==
* docker
* docker-compose
How to start
==
`docker-compose up -d`
How to monitor
==
See if it's running: `docker ps`
Check the logs: `docker-compose logs`
Notes when using CoreOs
==
Using CoreOs as host OS is entirely optional!
* the cloudconfig.yml file is a configuration file for starting a coreos machine
from scratch.
* when installing a Coreos server, docker-compose needs to be additionally installed next to the
already provided docker installation

View file

@ -0,0 +1,103 @@
#cloud-config
coreos:
update:
reboot-strategy: off
units:
- name: iptables-restore.service
enable: true
command: start
- name: create-swap.service
command: start
runtime: true
content: |
[Unit]
Description=Create swap file
Before=swap.service
[Service]
Type=oneshot
Environment="SWAPFILE=/2GiB.swap"
ExecStart=/usr/bin/touch ${SWAPFILE}
ExecStart=/usr/bin/chattr +C ${SWAPFILE}
ExecStart=/usr/bin/fallocate -l 2048m ${SWAPFILE}
ExecStart=/usr/bin/chmod 600 ${SWAPFILE}
ExecStart=/usr/sbin/mkswap ${SWAPFILE}
[Install]
WantedBy=multi-user.target
- name: swap.service
command: start
content: |
[Unit]
Description=Turn on swap
[Service]
Type=oneshot
Environment="SWAPFILE=/2GiB.swap"
RemainAfterExit=true
ExecStartPre=/usr/sbin/losetup -f ${SWAPFILE}
ExecStart=/usr/bin/sh -c "/sbin/swapon $(/usr/sbin/losetup -j ${SWAPFILE} | /usr/bin/cut -d : -f 1)"
ExecStop=/usr/bin/sh -c "/sbin/swapoff $(/usr/sbin/losetup -j ${SWAPFILE} | /usr/bin/cut -d : -f 1)"
ExecStopPost=/usr/bin/sh -c "/usr/sbin/losetup -d $(/usr/sbin/losetup -j ${SWAPFILE} | /usr/bin/cut -d : -f 1)"
[Install]
WantedBy=multi-user.target
- name: restart.service
content: |
[Unit]
Description=Restart docker containers
[Service]
Type=oneshot
ExecStart=/home/core/docker/restartContainers.sh
- name: restart.timer
command: start
content: |
[Unit]
Description=Restarts the app container 2 times a week
[Timer]
OnCalendar=Mon,Thu *-*-* 6:0:0
write_files:
- path: /etc/sysctl.d/swap.conf
permissions: 0644
owner: root
content: |
vm.swappiness=10
vm.vfs_cache_pressure=50
write_files:
- path: /etc/ssh/sshd_config
permissions: 0600
owner: root
content: |
# Use most defaults for sshd configuration.
UsePrivilegeSeparation sandbox
Subsystem sftp internal-sftp
UseDNS no
PermitRootLogin no
AllowUsers core
AuthenticationMethods publickey
write_files:
- path: /var/lib/iptables/rules-save
permissions: 0644
owner: 'root:root'
content: |
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -i eth1 -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 0 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 3 -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 11 -j ACCEPT
COMMIT
# the last line of the file needs to be a blank line or a comment

View file

@ -0,0 +1,21 @@
version: '3'
# Fill in your own BTCAVERAGE public and private keys
services:
pricenode:
restart: unless-stopped
build:
context: .
image: bisq:pricenode
ports:
- 80:80
- 8080:8080
environment:
- BTCAVERAGE_PRIVKEY=!!!!!!!!!!!!!!!!!!!!!!!!! YOUR PRIVATE KEY !!!!!!!!!!!!!!!!!!!!!!!!!!!
- BTCAVERAGE_PUBKEY=!!!!!!!!!!!!!!!!!!!!!!!!!! YOUR PUBKEY !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
entropy:
restart: always
image: harbur/haveged:1.7c-1
container_name: haveged-entropy
privileged: true

View file

@ -0,0 +1,4 @@
#!/bin/bash
mkdir -p /opt/bin
curl -L `curl -s https://api.github.com/repos/docker/compose/releases/latest | jq -r '.assets[].browser_download_url | select(contains("Linux") and contains("x86_64"))'` > /opt/bin/docker-compose
chmod +x /opt/bin/docker-compose

8
pricenode/docker/loop.sh Normal file
View file

@ -0,0 +1,8 @@
#!/bin/bash
while true
do
echo `date` "(Re)-starting node"
BITCOIN_AVG_PUBKEY=$BTCAVERAGE_PUBKEY BITCOIN_AVG_PRIVKEY=$BTCAVERAGE_PRIVKEY java -jar ./build/libs/bisq-pricenode.jar 2 2
echo `date` "node terminated unexpectedly!!"
sleep 3
done

View file

@ -0,0 +1,4 @@
#!/bin/sh
docker-compose build --no-cache && docker-compose up -d
docker image prune -f
docker-compose logs -f

View file

@ -0,0 +1 @@
nohup sh loop.sh

View file

@ -0,0 +1,4 @@
#!/bin/bash
# sudo -u debian-tor
nohup sudo -u debian-tor tor > /dev/null 2>errors_tor.log &

2
pricenode/docker/torrc Normal file
View file

@ -0,0 +1,2 @@
HiddenServiceDir /var/lib/tor/
HiddenServicePort 80 127.0.0.1:8080

View file

@ -0,0 +1,51 @@
/*
* 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 bisq.price;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Properties;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
new SpringApplicationBuilder(Main.class)
.properties(bisqProperties())
.run(args);
}
private static Properties bisqProperties() {
Properties props = new Properties();
File propsFile = new File(System.getenv("HOME"), ".config/bisq.properties");
if (propsFile.exists()) {
try {
props.load(new FileInputStream(propsFile));
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
return props;
}
}

View file

@ -0,0 +1,35 @@
/*
* 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 bisq.price;
import org.springframework.web.bind.annotation.ModelAttribute;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class PriceController {
protected final Logger log = LoggerFactory.getLogger(this.getClass());
@ModelAttribute
public void logRequest(HttpServletRequest request) {
log.info("Incoming {} request from: {}", request.getServletPath(), request.getHeader("User-Agent"));
}
}

View file

@ -0,0 +1,115 @@
/*
* 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 bisq.price;
import org.springframework.context.SmartLifecycle;
import java.time.Duration;
import java.util.Timer;
import java.util.TimerTask;
import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class PriceProvider<T> implements SmartLifecycle, Supplier<T> {
protected final Logger log = LoggerFactory.getLogger(this.getClass());
private final Timer timer = new Timer(true);
protected final Duration refreshInterval;
private T cachedResult;
public PriceProvider(Duration refreshInterval) {
this.refreshInterval = refreshInterval;
log.info("will refresh every {}", refreshInterval);
}
@Override
public final T get() {
if (!isRunning())
throw new IllegalStateException("call start() before calling get()");
return cachedResult;
}
@Override
public final void start() {
// we call refresh outside the context of a timer once at startup to ensure that
// any exceptions thrown get propagated and cause the application to halt
refresh();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
try {
refresh();
} catch (Throwable t) {
// we only log scheduled calls to refresh that fail to ensure that
// the application does *not* halt, assuming the failure is temporary
// and on the side of the upstream price provider, eg. BitcoinAverage
log.warn("refresh failed", t);
}
}
}, refreshInterval.toMillis(), refreshInterval.toMillis());
}
private void refresh() {
long ts = System.currentTimeMillis();
cachedResult = doGet();
log.info("refresh took {} ms.", (System.currentTimeMillis() - ts));
onRefresh();
}
protected abstract T doGet();
protected void onRefresh() {
}
@Override
public void stop() {
timer.cancel();
}
@Override
public void stop(Runnable callback) {
stop();
callback.run();
}
@Override
public boolean isAutoStartup() {
return true;
}
@Override
public boolean isRunning() {
return cachedResult != null;
}
@Override
public int getPhase() {
return 0;
}
}

View file

@ -0,0 +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 bisq.price.mining;
/**
* A value object representing the mining fee rate for a given base currency.
*/
public class FeeRate {
private final String currency;
private final long price;
private final long timestamp;
public FeeRate(String currency, long price, long timestamp) {
this.currency = currency;
this.price = price;
this.timestamp = timestamp;
}
public String getCurrency() {
return currency;
}
public long getPrice() {
return price;
}
public long getTimestamp() {
return timestamp;
}
}

View file

@ -0,0 +1,40 @@
/*
* 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 bisq.price.mining;
import bisq.price.PriceController;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
class FeeRateController extends PriceController {
private final FeeRateService feeRateService;
public FeeRateController(FeeRateService feeRateService) {
this.feeRateService = feeRateService;
}
@GetMapping(path = "/getFees")
public Map<String, Object> getFees() {
return feeRateService.getFees();
}
}

View file

@ -0,0 +1,32 @@
/*
* 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 bisq.price.mining;
import bisq.price.PriceProvider;
import java.time.Duration;
/**
* Abstract base class for providers of mining {@link FeeRate} data.
*/
public abstract class FeeRateProvider extends PriceProvider<FeeRate> {
public FeeRateProvider(Duration refreshInterval) {
super(refreshInterval);
}
}

View file

@ -0,0 +1,56 @@
/*
* 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 bisq.price.mining;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* High-level mining {@link FeeRate} operations.
*/
@Service
class FeeRateService {
private final Set<FeeRateProvider> providers;
public FeeRateService(Set<FeeRateProvider> providers) {
this.providers = providers;
}
public Map<String, Object> getFees() {
Map<String, Long> metadata = new HashMap<>();
Map<String, Long> allFeeRates = new HashMap<>();
providers.forEach(p -> {
FeeRate feeRate = p.get();
String currency = feeRate.getCurrency();
if ("BTC".equals(currency)) {
metadata.put("bitcoinFeesTs", feeRate.getTimestamp());
}
allFeeRates.put(currency.toLowerCase() + "TxFee", feeRate.getPrice());
});
return new HashMap<String, Object>() {{
putAll(metadata);
put("dataMap", allFeeRates);
}};
}
}

View file

@ -0,0 +1,121 @@
/*
* 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 bisq.price.mining.providers;
import bisq.price.PriceController;
import bisq.price.mining.FeeRate;
import bisq.price.mining.FeeRateProvider;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.env.CommandLinePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.http.RequestEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
@Component
class BitcoinFeeRateProvider extends FeeRateProvider {
private static final long MIN_FEE_RATE = 10; // satoshi/byte
private static final long MAX_FEE_RATE = 1000;
private static final int DEFAULT_MAX_BLOCKS = 2;
private static final int DEFAULT_REFRESH_INTERVAL = 2;
private final RestTemplate restTemplate = new RestTemplate();
private final int maxBlocks;
public BitcoinFeeRateProvider(Environment env) {
super(Duration.ofMinutes(refreshInterval(env)));
this.maxBlocks = maxBlocks(env);
}
protected FeeRate doGet() {
return new FeeRate("BTC", getEstimatedFeeRate(), Instant.now().getEpochSecond());
}
private long getEstimatedFeeRate() {
return getFeeRatePredictions()
.filter(p -> p.get("maxDelay") <= maxBlocks)
.findFirst()
.map(p -> p.get("maxFee"))
.map(r -> {
log.info("latest fee rate prediction is {} sat/byte", r);
return r;
})
.map(r -> Math.max(r, MIN_FEE_RATE))
.map(r -> Math.min(r, MAX_FEE_RATE))
.orElse(MIN_FEE_RATE);
}
private Stream<Map<String, Long>> getFeeRatePredictions() {
return restTemplate.exchange(
RequestEntity
.get(UriComponentsBuilder
// now using /fees/list because /fees/recommended estimates were too high
.fromUriString("https://bitcoinfees.earn.com/api/v1/fees/list")
.build().toUri())
.header("User-Agent", "") // required to avoid 403
.build(),
new ParameterizedTypeReference<Map<String, List<Map<String, Long>>>>() {
}
).getBody().entrySet().stream()
.flatMap(e -> e.getValue().stream());
}
private static Optional<String[]> args(Environment env) {
return Optional.ofNullable(
env.getProperty(CommandLinePropertySource.DEFAULT_NON_OPTION_ARGS_PROPERTY_NAME, String[].class));
}
private static int maxBlocks(Environment env) {
return args(env)
.filter(args -> args.length >= 1)
.map(args -> Integer.valueOf(args[0]))
.orElse(DEFAULT_MAX_BLOCKS);
}
private static long refreshInterval(Environment env) {
return args(env)
.filter(args -> args.length >= 2)
.map(args -> Integer.valueOf(args[1]))
.orElse(DEFAULT_REFRESH_INTERVAL);
}
@RestController
class Controller extends PriceController {
@GetMapping(path = "/getParams")
public String getParams() {
return String.format("%s;%s", maxBlocks, refreshInterval.toMillis());
}
}
}

View file

@ -0,0 +1,28 @@
/*
* 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 bisq.price.mining.providers;
import org.springframework.stereotype.Component;
@Component
class DashFeeRateProvider extends FixedFeeRateProvider {
public DashFeeRateProvider() {
super("DASH", 50);
}
}

View file

@ -0,0 +1,28 @@
/*
* 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 bisq.price.mining.providers;
import org.springframework.stereotype.Component;
@Component
class DogecoinFeeRateProvider extends FixedFeeRateProvider {
public DogecoinFeeRateProvider() {
super("DOGE", 5_000_000);
}
}

View file

@ -0,0 +1,40 @@
/*
* 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 bisq.price.mining.providers;
import bisq.price.mining.FeeRate;
import bisq.price.mining.FeeRateProvider;
import java.time.Duration;
import java.time.Instant;
abstract class FixedFeeRateProvider extends FeeRateProvider {
private final String currency;
private final long price;
public FixedFeeRateProvider(String currency, long price) {
super(Duration.ofDays(1));
this.currency = currency;
this.price = price;
}
protected final FeeRate doGet() {
return new FeeRate(currency, price, Instant.now().getEpochSecond());
}
}

View file

@ -0,0 +1,28 @@
/*
* 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 bisq.price.mining.providers;
import org.springframework.stereotype.Component;
@Component
class LitecoinFeeRateProvider extends FixedFeeRateProvider {
public LitecoinFeeRateProvider() {
super("LTC", 500);
}
}

View file

@ -0,0 +1,99 @@
/*
* 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 bisq.price.spot;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Objects;
/**
* A value object representing the spot price in bitcoin for a given currency at a given
* time as reported by a given provider.
*/
public class ExchangeRate {
private final String currency;
private final double price;
private final long timestamp;
private final String provider;
public ExchangeRate(String currency, BigDecimal price, Date timestamp, String provider) {
this(
currency,
price.doubleValue(),
timestamp.getTime(),
provider
);
}
public ExchangeRate(String currency, double price, long timestamp, String provider) {
this.currency = currency;
this.price = price;
this.timestamp = timestamp;
this.provider = provider;
}
@JsonProperty(value = "currencyCode", index = 1)
public String getCurrency() {
return currency;
}
@JsonProperty(value = "price", index = 2)
public double getPrice() {
return this.price;
}
@JsonProperty(value = "timestampSec", index = 3)
public long getTimestamp() {
return this.timestamp;
}
@JsonProperty(value = "provider", index = 4)
public String getProvider() {
return provider;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ExchangeRate exchangeRate = (ExchangeRate) o;
return Double.compare(exchangeRate.price, price) == 0 &&
timestamp == exchangeRate.timestamp &&
Objects.equals(currency, exchangeRate.currency) &&
Objects.equals(provider, exchangeRate.provider);
}
@Override
public int hashCode() {
return Objects.hash(currency, price, timestamp, provider);
}
@Override
public String toString() {
return "ExchangeRate{" +
"currency='" + currency + '\'' +
", price=" + price +
", timestamp=" + timestamp +
", provider=" + provider +
'}';
}
}

View file

@ -0,0 +1,40 @@
/*
* 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 bisq.price.spot;
import bisq.price.PriceController;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
class ExchangeRateController extends PriceController {
private final ExchangeRateService exchangeRateService;
public ExchangeRateController(ExchangeRateService exchangeRateService) {
this.exchangeRateService = exchangeRateService;
}
@GetMapping(path = "/getAllMarketPrices")
public Map<String, Object> getAllMarketPrices() {
return exchangeRateService.getAllMarketPrices();
}
}

View file

@ -0,0 +1,63 @@
/*
* 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 bisq.price.spot;
import bisq.price.PriceProvider;
import java.time.Duration;
import java.util.Set;
/**
* Abstract base class for providers of bitcoin {@link ExchangeRate} data. Implementations
* are marked with the {@link org.springframework.stereotype.Component} annotation in
* order to be discovered via classpath scanning. Implementations are also marked with the
* {@link org.springframework.core.annotation.Order} annotation to determine their
* precedence over each other in the case of two or more services returning exchange rate
* data for the same currency pair. In such cases, results from the provider with the
* higher order value will taking precedence over the provider with a lower value,
* presuming that such providers are being iterated over in an ordered list.
*
* @see ExchangeRateService#ExchangeRateService(java.util.List)
*/
public abstract class ExchangeRateProvider extends PriceProvider<Set<ExchangeRate>> {
private final String name;
private final String prefix;
public ExchangeRateProvider(String name, String prefix, Duration refreshInterval) {
super(refreshInterval);
this.name = name;
this.prefix = prefix;
}
public String getName() {
return name;
}
public String getPrefix() {
return prefix;
}
@Override
protected void onRefresh() {
get().stream()
.filter(e -> "USD".equals(e.getCurrency()) || "LTC".equals(e.getCurrency()))
.forEach(e -> log.info("BTC/{}: {}", e.getCurrency(), e.getPrice()));
}
}

View file

@ -0,0 +1,108 @@
/*
* 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 bisq.price.spot;
import bisq.price.spot.providers.BitcoinAverage;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* High-level {@link ExchangeRate} data operations.
*/
@Service
class ExchangeRateService {
protected final Logger log = LoggerFactory.getLogger(this.getClass());
private final List<ExchangeRateProvider> providers;
/**
* Construct an {@link ExchangeRateService} with a list of all
* {@link ExchangeRateProvider} implementations discovered via classpath scanning.
*
* @param providers all {@link ExchangeRateProvider} implementations in ascending
* order of precedence
*/
public ExchangeRateService(List<ExchangeRateProvider> providers) {
this.providers = providers;
}
public Map<String, Object> getAllMarketPrices() {
Map<String, Object> metadata = new LinkedHashMap<>();
Map<String, ExchangeRate> allExchangeRates = new LinkedHashMap<>();
providers.forEach(p -> {
Set<ExchangeRate> exchangeRates = p.get();
metadata.putAll(getMetadata(p, exchangeRates));
exchangeRates.forEach(e ->
allExchangeRates.put(e.getCurrency(), e)
);
});
return new LinkedHashMap<String, Object>() {{
putAll(metadata);
// Use a sorted list by currency code to make comparision of json data between different
// price nodes easier
List<ExchangeRate> values = new ArrayList<>(allExchangeRates.values());
values.sort(Comparator.comparing(ExchangeRate::getCurrency));
put("data", values);
}};
}
private Map<String, Object> getMetadata(ExchangeRateProvider provider, Set<ExchangeRate> exchangeRates) {
Map<String, Object> metadata = new LinkedHashMap<>();
// In case a provider is not available we still want to deliver the data of the other providers, so we catch
// a possible exception and leave timestamp at 0. The Bisq app will check if the timestamp is in a tolerance
// window and if it is too old it will show that the price is not available.
long timestamp = 0;
try {
timestamp = getTimestamp(provider, exchangeRates);
} catch (Throwable t) {
log.error(t.toString());
t.printStackTrace();
}
if (provider instanceof BitcoinAverage.Local) {
metadata.put("btcAverageTs", timestamp);
}
String prefix = provider.getPrefix();
metadata.put(prefix + "Ts", timestamp);
metadata.put(prefix + "Count", exchangeRates.size());
return metadata;
}
private long getTimestamp(ExchangeRateProvider provider, Set<ExchangeRate> exchangeRates) {
return exchangeRates.stream()
.filter(e -> provider.getName().equals(e.getProvider()))
.findFirst()
.orElseThrow(() -> new IllegalStateException("No exchange rate data found for " + provider.getName()))
.getTimestamp();
}
}

View file

@ -0,0 +1,158 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.bitcoinaverage.dto.marketdata.BitcoinAverageTicker;
import org.knowm.xchange.bitcoinaverage.dto.marketdata.BitcoinAverageTickers;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.http.RequestEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import org.bouncycastle.util.encoders.Hex;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* See the BitcoinAverage API documentation at https://apiv2.bitcoinaverage.com/#ticker-data-all
*/
public abstract class BitcoinAverage extends ExchangeRateProvider {
/**
* Max number of requests allowed per month on the BitcoinAverage developer plan.
* Note the actual max value is 45,000; we use the more conservative value below to
* ensure we do not exceed it. See https://bitcoinaverage.com/en/plans.
*/
private static final double MAX_REQUESTS_PER_MONTH = 42_514;
private final RestTemplate restTemplate = new RestTemplate();
private final String symbolSet;
private String pubKey;
private Mac mac;
/**
* @param symbolSet "global" or "local"; see https://apiv2.bitcoinaverage.com/#supported-currencies
*/
public BitcoinAverage(String name, String prefix, double pctMaxRequests, String symbolSet, Environment env) {
super(name, prefix, refreshIntervalFor(pctMaxRequests));
this.symbolSet = symbolSet;
this.pubKey = env.getRequiredProperty("BITCOIN_AVG_PUBKEY");
this.mac = initMac(env.getRequiredProperty("BITCOIN_AVG_PRIVKEY"));
}
@Override
public Set<ExchangeRate> doGet() {
return getTickersKeyedByCurrency().entrySet().stream()
.filter(e -> supportedCurrency(e.getKey()))
.map(e ->
new ExchangeRate(
e.getKey(),
e.getValue().getLast(),
e.getValue().getTimestamp(),
this.getName()
)
)
.collect(Collectors.toSet());
}
private boolean supportedCurrency(String currencyCode) {
// ignore Venezuelan bolivars as the "official" exchange rate is just wishful thinking
// we should use this API with a custom provider instead: http://api.bitcoinvenezuela.com/1
return !"VEF".equals(currencyCode);
}
private Map<String, BitcoinAverageTicker> getTickersKeyedByCurrency() {
// go from a map with keys like "BTCUSD", "BTCVEF"
return getTickersKeyedByCurrencyPair().entrySet().stream()
// to a map with keys like "USD", "VEF"
.collect(Collectors.toMap(e -> e.getKey().substring(3), Map.Entry::getValue));
}
private Map<String, BitcoinAverageTicker> getTickersKeyedByCurrencyPair() {
return restTemplate.exchange(
RequestEntity
.get(UriComponentsBuilder
.fromUriString("https://apiv2.bitcoinaverage.com/indices/{symbol-set}/ticker/all?crypto=BTC")
.buildAndExpand(symbolSet)
.toUri())
.header("X-signature", getAuthSignature())
.build(),
BitcoinAverageTickers.class
).getBody().getTickers();
}
protected String getAuthSignature() {
String payload = String.format("%s.%s", Instant.now().getEpochSecond(), pubKey);
return String.format("%s.%s", payload, Hex.toHexString(mac.doFinal(payload.getBytes())));
}
private static Mac initMac(String privKey) {
String algorithm = "HmacSHA256";
SecretKey secretKey = new SecretKeySpec(privKey.getBytes(), algorithm);
try {
Mac mac = Mac.getInstance(algorithm);
mac.init(secretKey);
return mac;
} catch (NoSuchAlgorithmException | InvalidKeyException ex) {
throw new RuntimeException(ex);
}
}
private static Duration refreshIntervalFor(double pctMaxRequests) {
long requestsPerMonth = (long) (MAX_REQUESTS_PER_MONTH * pctMaxRequests);
return Duration.ofDays(31).dividedBy(requestsPerMonth);
}
@Component
@Order(1)
public static class Global extends BitcoinAverage {
public Global(Environment env) {
super("BTCA_G", "btcAverageG", 0.3, "global", env);
}
}
@Component
@Order(2)
public static class Local extends BitcoinAverage {
public Local(Environment env) {
super("BTCA_L", "btcAverageL", 0.7, "local", env);
}
}
}

View file

@ -0,0 +1,77 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import bisq.price.util.Altcoins;
import org.knowm.xchange.coinmarketcap.dto.marketdata.CoinMarketCapTicker;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.annotation.Order;
import org.springframework.http.RequestEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.time.Duration;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Component
@Order(3)
class CoinMarketCap extends ExchangeRateProvider {
private final RestTemplate restTemplate = new RestTemplate();
public CoinMarketCap() {
super("CMC", "coinmarketcap", Duration.ofMinutes(5)); // large data structure, so don't request it too often
}
@Override
public Set<ExchangeRate> doGet() {
return getTickers()
.filter(t -> Altcoins.ALL_SUPPORTED.contains(t.getIsoCode()))
.map(t ->
new ExchangeRate(
t.getIsoCode(),
t.getPriceBTC(),
t.getLastUpdated(),
this.getName()
)
)
.collect(Collectors.toSet());
}
private Stream<CoinMarketCapTicker> getTickers() {
return restTemplate.exchange(
RequestEntity
.get(UriComponentsBuilder
.fromUriString("https://api.coinmarketcap.com/v1/ticker/?limit=200").build()
.toUri())
.build(),
new ParameterizedTypeReference<List<CoinMarketCapTicker>>() {
}
).getBody().stream();
}
}

View file

@ -0,0 +1,94 @@
/*
* 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 bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import bisq.price.util.Altcoins;
import org.knowm.xchange.currency.Currency;
import org.knowm.xchange.currency.CurrencyPair;
import org.knowm.xchange.poloniex.dto.marketdata.PoloniexMarketData;
import org.knowm.xchange.poloniex.dto.marketdata.PoloniexTicker;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.annotation.Order;
import org.springframework.http.RequestEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.time.Duration;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Component
@Order(4)
class Poloniex extends ExchangeRateProvider {
private final RestTemplate restTemplate = new RestTemplate();
public Poloniex() {
super("POLO", "poloniex", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
Date timestamp = new Date(); // Poloniex tickers don't include their own timestamp
return getTickers()
.filter(t -> t.getCurrencyPair().base.equals(Currency.BTC))
.filter(t -> Altcoins.ALL_SUPPORTED.contains(t.getCurrencyPair().counter.getCurrencyCode()))
.map(t ->
new ExchangeRate(
t.getCurrencyPair().counter.getCurrencyCode(),
t.getPoloniexMarketData().getLast(),
timestamp,
this.getName()
)
)
.collect(Collectors.toSet());
}
private Stream<PoloniexTicker> getTickers() {
return getTickersKeyedByCurrencyPair().entrySet().stream()
.map(e -> {
String pair = e.getKey();
PoloniexMarketData data = e.getValue();
String[] symbols = pair.split("_"); // e.g. BTC_USD => [BTC, USD]
return new PoloniexTicker(data, new CurrencyPair(symbols[0], symbols[1]));
});
}
private Map<String, PoloniexMarketData> getTickersKeyedByCurrencyPair() {
return restTemplate.exchange(
RequestEntity
.get(UriComponentsBuilder
.fromUriString("https://poloniex.com/public?command=returnTicker").build()
.toUri())
.build(),
new ParameterizedTypeReference<Map<String, PoloniexMarketData>>() {
}
).getBody();
}
}

View file

@ -0,0 +1,32 @@
/*
* 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 bisq.price.util;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.TradeCurrency;
import java.util.Set;
import java.util.stream.Collectors;
public abstract class Altcoins {
public static final Set<String> ALL_SUPPORTED =
CurrencyUtil.getAllSortedCryptoCurrencies().stream()
.map(TradeCurrency::getCode)
.collect(Collectors.toSet());
}

View file

@ -0,0 +1,55 @@
/*
* 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 bisq.price.util;
import bisq.price.PriceController;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.core.io.Resource;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.io.InputStreamReader;
@RestController
class VersionController extends PriceController implements InfoContributor {
private final String version;
public VersionController(@Value("classpath:version.txt") Resource versionTxt) throws IOException {
this.version = FileCopyUtils.copyToString(
new InputStreamReader(
versionTxt.getInputStream()
)
).trim();
}
@GetMapping(path = "/getVersion")
public String getVersion() {
return version;
}
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("version", version);
}
}

View file

@ -0,0 +1 @@
spring.jackson.serialization.indent_output=true

View file

@ -0,0 +1,6 @@
__ _ _ __
/ /_ (_)________ _ ____ _____(_)_______ ____ ____ ____/ /__
/ __ \/ / ___/ __ `/_____/ __ \/ ___/ / ___/ _ \/ __ \/ __ \/ __ / _ \
/ /_/ / (__ ) /_/ /_____/ /_/ / / / / /__/ __/ / / / /_/ / /_/ / __/
/_.___/_/____/\__, / / .___/_/ /_/\___/\___/_/ /_/\____/\__,_/\___/
/_/ /_/ ${application.formatted-version}

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%highlight(%d{MMM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{15}: %msg %xEx%n)</pattern>
</encoder>
</appender>
<root level="WARN">
<appender-ref ref="CONSOLE_APPENDER"/>
</root>
<logger name="bisq" level="INFO"/>
<logger name="org.springframework.boot.context.embedded.tomcat" level="INFO"/>
</configuration>

View file

@ -0,0 +1 @@
0.7.2-SNAPSHOT

2
pricenode/torrc Normal file
View file

@ -0,0 +1,2 @@
HiddenServiceDir build/tor-hidden-service
HiddenServicePort 80 127.0.0.1:8080

View file

@ -4,5 +4,6 @@ include 'p2p'
include 'core'
include 'desktop'
include 'monitor'
include 'pricenode'
rootProject.name = 'bisq'