mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-22 06:41:41 +01:00
Merge pricenode repository at 85df033
This commit is contained in:
parent
ecef18a437
commit
9a7b761549
41 changed files with 1687 additions and 0 deletions
1
pricenode/Procfile
Normal file
1
pricenode/Procfile
Normal 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
|
43
pricenode/README-HEROKU.md
Normal file
43
pricenode/README-HEROKU.md
Normal 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
20
pricenode/TODO.md
Normal 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
34
pricenode/build.gradle
Normal 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
|
||||
}
|
26
pricenode/docker/Dockerfile
Normal file
26
pricenode/docker/Dockerfile
Normal 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
|
43
pricenode/docker/README.md
Normal file
43
pricenode/docker/README.md
Normal 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
|
103
pricenode/docker/cloudconfig.yml
Normal file
103
pricenode/docker/cloudconfig.yml
Normal 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
|
21
pricenode/docker/docker-compose.yml
Normal file
21
pricenode/docker/docker-compose.yml
Normal 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
|
4
pricenode/docker/installDockerCompose.sh
Normal file
4
pricenode/docker/installDockerCompose.sh
Normal 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
8
pricenode/docker/loop.sh
Normal 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
|
4
pricenode/docker/rebuildAndRestart.sh
Executable file
4
pricenode/docker/rebuildAndRestart.sh
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
docker-compose build --no-cache && docker-compose up -d
|
||||
docker image prune -f
|
||||
docker-compose logs -f
|
1
pricenode/docker/start_node.sh
Normal file
1
pricenode/docker/start_node.sh
Normal file
|
@ -0,0 +1 @@
|
|||
nohup sh loop.sh
|
4
pricenode/docker/start_tor.sh
Normal file
4
pricenode/docker/start_tor.sh
Normal 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
2
pricenode/docker/torrc
Normal file
|
@ -0,0 +1,2 @@
|
|||
HiddenServiceDir /var/lib/tor/
|
||||
HiddenServicePort 80 127.0.0.1:8080
|
51
pricenode/src/main/java/bisq/price/Main.java
Normal file
51
pricenode/src/main/java/bisq/price/Main.java
Normal 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;
|
||||
}
|
||||
}
|
35
pricenode/src/main/java/bisq/price/PriceController.java
Normal file
35
pricenode/src/main/java/bisq/price/PriceController.java
Normal 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"));
|
||||
}
|
||||
}
|
115
pricenode/src/main/java/bisq/price/PriceProvider.java
Normal file
115
pricenode/src/main/java/bisq/price/PriceProvider.java
Normal 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;
|
||||
}
|
||||
}
|
46
pricenode/src/main/java/bisq/price/mining/FeeRate.java
Normal file
46
pricenode/src/main/java/bisq/price/mining/FeeRate.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}};
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
99
pricenode/src/main/java/bisq/price/spot/ExchangeRate.java
Normal file
99
pricenode/src/main/java/bisq/price/spot/ExchangeRate.java
Normal 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 +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
108
pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java
Normal file
108
pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
32
pricenode/src/main/java/bisq/price/util/Altcoins.java
Normal file
32
pricenode/src/main/java/bisq/price/util/Altcoins.java
Normal 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());
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
1
pricenode/src/main/resources/application.properties
Normal file
1
pricenode/src/main/resources/application.properties
Normal file
|
@ -0,0 +1 @@
|
|||
spring.jackson.serialization.indent_output=true
|
6
pricenode/src/main/resources/banner.txt
Normal file
6
pricenode/src/main/resources/banner.txt
Normal file
|
@ -0,0 +1,6 @@
|
|||
__ _ _ __
|
||||
/ /_ (_)________ _ ____ _____(_)_______ ____ ____ ____/ /__
|
||||
/ __ \/ / ___/ __ `/_____/ __ \/ ___/ / ___/ _ \/ __ \/ __ \/ __ / _ \
|
||||
/ /_/ / (__ ) /_/ /_____/ /_/ / / / / /__/ __/ / / / /_/ / /_/ / __/
|
||||
/_.___/_/____/\__, / / .___/_/ /_/\___/\___/_/ /_/\____/\__,_/\___/
|
||||
/_/ /_/ ${application.formatted-version}
|
16
pricenode/src/main/resources/logback.xml
Normal file
16
pricenode/src/main/resources/logback.xml
Normal 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>
|
1
pricenode/src/main/resources/version.txt
Normal file
1
pricenode/src/main/resources/version.txt
Normal file
|
@ -0,0 +1 @@
|
|||
0.7.2-SNAPSHOT
|
2
pricenode/torrc
Normal file
2
pricenode/torrc
Normal file
|
@ -0,0 +1,2 @@
|
|||
HiddenServiceDir build/tor-hidden-service
|
||||
HiddenServicePort 80 127.0.0.1:8080
|
|
@ -4,5 +4,6 @@ include 'p2p'
|
|||
include 'core'
|
||||
include 'desktop'
|
||||
include 'monitor'
|
||||
include 'pricenode'
|
||||
|
||||
rootProject.name = 'bisq'
|
||||
|
|
Loading…
Add table
Reference in a new issue