Merge pull request #6430 from HenrikJannsen/remove-unused-modules

Remove unused modules
This commit is contained in:
Alejandro García 2022-11-29 14:45:37 +02:00 committed by GitHub
commit 28c4da3212
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
124 changed files with 7 additions and 11542 deletions

2
.gitignore vendored
View file

@ -30,8 +30,6 @@ desktop.ini
*.class
deploy
*/releases/*
/monitor/TorHiddenServiceStartupTimeTests/*
/monitor/monitor-tor/*
.java-version
.localnet
/apitest/src/main/resources/dao-setup*

View file

@ -37,9 +37,6 @@
#
# $ ls -1 bisq-*
# bisq-desktop
# bisq-monitor
# bisq-pricenode
# bisq-relay
# bisq-seednode
# bisq-statsnode
#

View file

@ -9,7 +9,6 @@ buildscript {
classpath 'com.google.gradle:osdetector-gradle-plugin:1.6.0'
classpath 'com.github.jengelman.gradle.plugins:shadow:5.2.0'
classpath 'org.openjfx:javafx-plugin:0.0.10'
classpath 'org.springframework.boot:spring-boot-gradle-plugin:2.5.6'
}
}
@ -61,7 +60,6 @@ configure(subprojects) {
junitVersion = '4.12'
jupiterVersion = '5.7.0'
kotlinVersion = '1.3.41'
knowmXchangeVersion = '5.0.13'
langVersion = '3.11'
logbackVersion = '1.1.11'
loggingVersion = '1.2'
@ -72,7 +70,6 @@ configure(subprojects) {
protocVersion = protobufVersion
qrgenVersion = '1.3'
slf4jVersion = '1.7.30'
springBootVersion = '2.5.6'
os = osdetector.os == 'osx' ? 'mac' : osdetector.os == 'windows' ? 'win' : osdetector.os
}
@ -95,10 +92,8 @@ configure(subprojects) {
configure([project(':cli'),
project(':daemon'),
project(':desktop'),
project(':monitor'),
project(':seednode'),
project(':statsnode'),
project(':pricenode'),
project(':apitest')]) {
apply plugin: 'application'
@ -518,126 +513,6 @@ configure(project(':desktop')) {
}
configure(project(':monitor')) {
apply plugin: 'org.openjfx.javafxplugin'
javafx {
version = "$javafxVersion"
modules = ['javafx.base']
}
mainClassName = 'bisq.monitor.Monitor'
test {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed"
}
}
dependencies {
implementation project(':assets')
implementation project(':common')
implementation project(':core')
implementation project(':p2p')
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
compileOnly "org.projectlombok:lombok:$lombokVersion"
implementation "ch.qos.logback:logback-classic:$logbackVersion"
implementation "ch.qos.logback:logback-core:$logbackVersion"
implementation "com.google.guava:guava:$guavaVersion"
implementation "org.slf4j:slf4j-api:$slf4jVersion"
implementation("com.github.bisq-network.netlayer:tor.external:$netlayerVersion") {
exclude(module: 'slf4j-api')
}
implementation("com.github.bisq-network.netlayer:tor.native:$netlayerVersion") {
exclude(module: 'slf4j-api')
}
implementation("com.google.inject:guice:$guiceVersion") {
exclude(module: 'guava')
}
testAnnotationProcessor "org.projectlombok:lombok:$lombokVersion"
testCompileOnly "org.projectlombok:lombok:$lombokVersion"
testImplementation "org.junit.jupiter:junit-jupiter-api:$jupiterVersion"
testImplementation "org.junit.jupiter:junit-jupiter-params:$jupiterVersion"
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$jupiterVersion")
}
}
configure(project(':pricenode')) {
apply plugin: "org.springframework.boot"
apply plugin: 'io.spring.dependency-management'
mainClassName = 'bisq.price.Main'
version = file("src/main/resources/version.txt").text.trim()
jar.manifest.attributes(
"Implementation-Title": project.name,
"Implementation-Version": version)
ext['log4j2.version'] = '2.17.0'
dependencies {
implementation project(":common")
implementation project(":core")
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
compileOnly "org.projectlombok:lombok:$lombokVersion"
implementation "com.google.code.gson:gson:$gsonVersion"
implementation "com.google.guava:guava:$guavaVersion"
implementation "commons-codec:commons-codec:$codecVersion"
implementation "org.apache.httpcomponents:httpcore:$httpcoreVersion"
implementation("org.apache.httpcomponents:httpclient:$httpclientVersion") {
exclude(module: 'commons-codec')
}
implementation("org.knowm.xchange:xchange-binance:$knowmXchangeVersion")
implementation("org.knowm.xchange:xchange-bitbay:$knowmXchangeVersion")
implementation("org.knowm.xchange:xchange-bitfinex:$knowmXchangeVersion")
implementation("org.knowm.xchange:xchange-bitflyer:$knowmXchangeVersion")
implementation("org.knowm.xchange:xchange-bitstamp:$knowmXchangeVersion")
implementation("org.knowm.xchange:xchange-btcmarkets:$knowmXchangeVersion")
implementation("org.knowm.xchange:xchange-cexio:$knowmXchangeVersion")
implementation("org.knowm.xchange:xchange-coinbasepro:$knowmXchangeVersion")
implementation("org.knowm.xchange:xchange-coinmarketcap:$knowmXchangeVersion")
implementation("org.knowm.xchange:xchange-coinmate:$knowmXchangeVersion")
implementation("org.knowm.xchange:xchange-coinone:$knowmXchangeVersion")
implementation("org.knowm.xchange:xchange-exmo:$knowmXchangeVersion")
implementation("org.knowm.xchange:xchange-hitbtc:$knowmXchangeVersion")
implementation("org.knowm.xchange:xchange-huobi:$knowmXchangeVersion")
implementation("org.knowm.xchange:xchange-independentreserve:$knowmXchangeVersion")
implementation("org.knowm.xchange:xchange-kraken:$knowmXchangeVersion")
implementation("org.knowm.xchange:xchange-luno:$knowmXchangeVersion")
implementation("org.knowm.xchange:xchange-mercadobitcoin:$knowmXchangeVersion")
implementation("org.knowm.xchange:xchange-paribu:$knowmXchangeVersion")
implementation("org.knowm.xchange:xchange-poloniex:$knowmXchangeVersion")
implementation("org.knowm.xchange:xchange-quoine:$knowmXchangeVersion")
implementation("org.springframework.boot:spring-boot-starter-web:$springBootVersion")
testAnnotationProcessor "org.projectlombok:lombok:$lombokVersion"
testCompileOnly "org.projectlombok:lombok:$lombokVersion"
testImplementation "org.junit.jupiter:junit-jupiter-api:$jupiterVersion"
testImplementation "org.junit.jupiter:junit-jupiter-params:$jupiterVersion"
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$jupiterVersion")
}
test {
useJUnitPlatform()
// Disabled by default, since spot provider tests include connections to external API endpoints
// Can be enabled by adding -Dtest.pricenode.includeSpotProviderTests=true to the gradle command:
// ./gradlew test -Dtest.pricenode.includeSpotProviderTests=true
if (System.properties['test.pricenode.includeSpotProviderTests'] != 'true') {
project.logger.lifecycle('Pricenode: Skipping spot provider tests')
exclude 'bisq/price/spot/providers/**'
}
}
task stage {
dependsOn assemble
}
}
configure(project(':seednode')) {
apply plugin: 'com.github.johnrengelman.shadow'

File diff suppressed because it is too large Load diff

View file

@ -1,246 +0,0 @@
# Bisq Network Monitor Node
The Bisq monitor node collects a set of metrics which are of interest to developers and users alike. These metrics are then made available through reporters.
The *Settled* release features these metrics:
- Tor Startup Time: The time it takes to start Tor starting at a clean system, unpacking the shipped Tor binaries, firing up Tor until Tor is connected to the Tor network and ready to use.
- Tor Roundtrip Time: Given a bootstrapped Tor, the roundtrip time of connecting to a hidden service is measured.
- Tor Hidden Service Startup Time: Given a bootstrapped Tor, the time it takes to create and announce a freshly created hidden service.
- P2P Round Trip Time: A metric hitchhiking the Ping/Pong messages of the Keep-Alive-Mechanism to determine the Round Trip Time when issuing a Ping to a seed node.
- P2P Seed Node Message Snapshot: Get absolute number and constellation of messages a fresh Bisq client will get on startup. Also reports diffs between seed nodes on a per-message-type basis.
- P2P Network Load: listens to the P2P network and its broadcast messages. Reports every X seconds.
- P2P Market Statistics: a demonstration metric which extracts market information from broadcast messages. This demo implementation reports the number of open offers per market.
The *Settled* release features these reporters:
- A reporter that simply writes the findings to `System.err`
- A reporter that reports the findings to a Graphite/Carbon instance using the [plaintext protocol](https://graphite.readthedocs.io/en/latest/feeding-carbon.html#the-plaintext-protocol)
## Configuration
The *Bisq Network Monitor Node* is to be configured via a Java properties file. There is a default configuration file shipped with the monitor which reports to the one monitoring service currently up and running.
If you want to tweak the configuration, you can pass the location of the file as command line parameter:
```
./bisq-monitor /path/to/your/config.properties
```
A sample configuration file looks like follows:
```
## System configuration
# true overwrites the reporters picked by the developers (for debugging for example) (defaults to false)
System.useConsoleReporter=true
# 0 -> BTC_MAINNET, 1 -> BTC_TESTNET (default)
System.baseCurrencyNetwork=0
## Each Metric is configured via a set of properties.
##
## The minimal set of properties required to run a Metric is:
##
## YourMetricName.enabled=true|false
## YourMetricName.run.interval=10 [seconds]
#Edit and uncomment the lines below to your liking
#TorStartupTime Metric
TorStartupTime.enabled=true
TorStartupTime.run.interval=100
TorStartupTime.run.socksPort=90500 # so that there is no interference with a system Tor
#TorRoundTripTime Metric
TorRoundTripTime.enabled=true
TorRoundTripTime.run.interval=100
TorRoundTripTime.run.sampleSize=5
TorRoundTripTime.run.hosts=http://expyuzz4wqqyqhjn.onion:80 # torproject.org hidden service
#TorHiddenServiceStartupTime Metric
TorHiddenServiceStartupTime.enabled=true
TorHiddenServiceStartupTime.run.interval=100
TorHiddenServiceStartupTime.run.localPort=90501 # so that there is no interference with a system Tor
TorHiddenServiceStartupTime.run.servicePort=90511 # so that there is no interference with a system Tor
#P2PRoundTripTime Metric
P2PRoundTripTime.enabled=true
P2PRoundTripTime.run.interval=100
P2PRoundTripTime.run.sampleSize=5
P2PRoundTripTime.run.hosts=723ljisnynbtdohi.onion:8000, fl3mmribyxgrv63c.onion:8000
P2PRoundTripTime.run.torProxyPort=9060
#P2PNetworkLoad Metric
P2PNetworkLoad.enabled=true
P2PNetworkLoad.run.interval=100
P2PNetworkLoad.run.torProxyPort=9061
P2PNetworkLoad.run.historySize=500
#P2PNetworkMessageSnapshot Metric
P2PSeedNodeSnapshot.enabled=true
P2PSeedNodeSnapshot.run.interval=24
P2PSeedNodeSnapshot.run.hosts=3f3cu2yw7u457ztq.onion:8000, 723ljisnynbtdohi.onion:8000, fl3mmribyxgrv63c.onion:8000
P2PSeedNodeSnapshot.run.torProxyPort=9062
#P2PMarketStats Metric
P2PMarketStats.enabled=true
P2PMarketStats.run.interval=37
P2PMarketStats.run.hosts=ef5qnzx6znifo3df.onion:8000
P2PMarketStats.run.torProxyPort=9063
#PriceNodeStats Metric
PriceNodeStats.enabled=true
PriceNodeStats.run.interval=42
PriceNodeStats.run.hosts=http://5bmpx76qllutpcyp.onion, http://xc3nh4juf2hshy7e.onion, http://44mgyoe2b6oqiytt.onion, http://62nvujg5iou3vu3i.onion, http://ceaanhbvluug4we6.onion
#MarketStats Metric
MarketStats.enabled=true
MarketStats.run.interval=191
## Reporters are configured via a set of properties as well.
##
## In contrast to Metrics, Reporters do not have a minimal set of properties.
#GraphiteReporter
GraphiteReporter.serviceUrl=k6evlhg44acpchtc.onion:2003
```
## Run
The distribution ships with a systemd .service file. Validate/change the executable/config paths within the shipped `bisq-monitor.service` file and copy/move the file to your systemd directory (something along `/usr/lib/systemd/system/`). Now you can control your *Monitor Node* via the usual systemd start/stop commands
```
systemctl start bisq-monitor.service
systemctl stop bisq-monitor.service
```
and
```
systemctl enable bisq-monitor.service
```
You can reload the configuration without restarting the service by using
```
systemctl reload bisq-monitor.service
```
Follow the logs created by the service by inspecting
```
journalctl --unit bisq-monitor --follow
```
# Monitoring Service
A typical monitoring service consists of a [Graphite](https://graphiteapp.org/) and a [Grafana](https://grafana.com/) instance.
Both are available via Docker-containers.
## Setting up Graphite
### Install
For a docker setup, use
```
docker run -d --name graphite --restart=always -p 2003:2003 -p 8080:8080 graphiteapp/graphite-statsd
```
- Port 2003 is used for the [plaintext protocol](https://graphite.readthedocs.io/en/latest/feeding-carbon.html#the-plaintext-protocol) mentioned above
- Port 8080 offers an API for user interfaces.
more information can be found [here](https://graphite.readthedocs.io/en/latest/install.html)
### Configuration
For configuration, you must adapt the whisper database schema to suit your needs. First, stop your docker container by running
```
docker stop graphite
```
Find your config files within the `Source` directory stated in
```
docker inspect graphite | grep -C 2 graphite/conf\",
```
Edit `storage-schemas.conf` so that the frequency of your incoming data (configured in the monitor configs `interval`) is matched. For example, insert
```
[bisq]
pattern = ^bisq.*
retentions = 10s:1h,5m:31d,30m:2y,1h:5y
```
before the `[default...` blocks of the file. This basically says, that every incoming set of data reflects 5 minutes of the time series. Furthermore, every 30 minutes, the data is compressed and thus, takes less memory as it is kept for 2 years.
Further, edit `storage-aggregation.conf` to configure how your data is compressed. For example, insert
```
[bisq]
pattern=^bisq.*
xFilesFactor = 0
aggregationMethod = average
```
before the `[default...` blocks of the file. With this configuration, whenever data is aggregated, the `average` data is made available given that at least `0%` of the data points (i.e. floor(30 / 5 * 40%) = 2 data points) exist. Otherwise, the aggregated data is dropped. Since we start the first hour with a frequency of 10s but only supply data every 4 to 6 minutes, our aggregated values would get dropped.
*Please note, that I have not been able to get the whole thing to work without the 10s:1h part yet*
Finally, update the database. For doing that, go to the storage directory of graphite, the `Source` directory stated in
```
docker inspect graphite | grep -C 2 graphite/conf\",
```
Once there, you have two options:
- delete the whisper directory
```
rm -r whisper
```
- update the database by doing
```
find ./ -type f -name '*.wsp' -exec whisper-resize.py --nobackup {} 10s:1h 5m:31d 30m:2y 1h:5y \;
```
and finally, restart your graphite container:
```
docker start graphite
```
Other than that, there is no further configuration necessary. However, you might change your iptables/firewalls to not let anyone access your Graphite instance from the outside.
### Backup your data
The metric data is kept in the `Source` directory stated in
```
docker inspect graphite | grep -C 2 graphite/conf\",
```
ready to be backed up regularly.
## Setting up Grafana
### Install
For a docker setup, use
```
docker run -d --name=grafana -p 3000:3000 grafana/grafana
```
- Port 3000 offers the web interface
more information can be found [here](https://grafana.com/grafana/download?platform=docker)
### Configuration
- Once you have Grafana up and running, go to the *Data Source* configuration tab.
- Once there click *Add data source* and select *Graphite*.
- In the HTTP section enter the IP address of your graphite docker container and the port `8080` (as we have configured before). E.g. `http://172.170.1:8080`
- Select `Server (default)` as an *Access* method and hit *Save & Test*.
You should be all set. You can now proceed to add Dashboards, Panels and finally display the prettiest Graphs you can think of.
A working connection to Graphite should let you add your data series in a *Graph*s *Metrics* tab in a pretty intuitive way.
- Optional: hide your Grafana instance behind a reverse proxy like nginx and add some TLS.
- Optional: make your Grafana instance accessible via a Tor hidden service.
### Backup your data
Grafana stores every dashboard as a JSON model. This model can be accessed (copied/restored) within the dashboard's settings and its *JSON Model* tab. Do with the data whatever you want.

View file

@ -1,16 +0,0 @@
[Unit]
Description=Bisq network monitor
After=network.target
[Service]
WorkingDirectory=~
Environment="JAVA_OPTS='-Xmx500M'"
ExecStart=/home/bisq/bisq/bisq-monitor /home/bisq/monitor.properties
ExecReload=/bin/kill -USR1 $MAINPID
Restart=on-failure
User=bisq
Group=bisq
[Install]
WantedBy=multi-user.target

View file

@ -1,136 +0,0 @@
Hostname "__ONION_ADDRESS__"
Interval 30
LoadPlugin syslog
<Plugin syslog>
LogLevel info
</Plugin>
LoadPlugin cpu
LoadPlugin df
LoadPlugin disk
LoadPlugin fhcount
LoadPlugin interface
LoadPlugin java
LoadPlugin load
LoadPlugin memory
LoadPlugin processes
LoadPlugin swap
LoadPlugin write_graphite
<Plugin cpu>
ReportByCpu true
ValuesPercentage true
</Plugin>
<Plugin df>
MountPoint "/"
</Plugin>
<Plugin disk>
Disk "/[hs]da/"
</Plugin>
<Plugin fhcount>
ValuesAbsolute false
ValuesPercentage true
</Plugin>
<Plugin interface>
Interface "eth0"
</Plugin>
<Plugin java>
JVMArg "-verbose:jni"
JVMArg "-Djava.class.path=/usr/share/collectd/java/collectd-api.jar:/usr/share/collectd/java/generic-jmx.jar"
LoadPlugin "org.collectd.java.GenericJMX"
<Plugin "GenericJMX">
# Generic heap/nonheap memory usage.
<MBean "memory">
ObjectName "java.lang:type=Memory"
#InstanceFrom ""
InstancePrefix "memory"
# Creates four values: committed, init, max, used
<Value>
Type "memory"
#InstancePrefix ""
#InstanceFrom ""
Table true
Attribute "HeapMemoryUsage"
InstancePrefix "heap-"
</Value>
# Creates four values: committed, init, max, used
<Value>
Type "memory"
#InstancePrefix ""
#InstanceFrom ""
Table true
Attribute "NonHeapMemoryUsage"
InstancePrefix "nonheap-"
</Value>
</MBean>
# Memory usage by memory pool.
<MBean "memory_pool">
ObjectName "java.lang:type=MemoryPool,*"
InstancePrefix "memory_pool-"
InstanceFrom "name"
<Value>
Type "memory"
#InstancePrefix ""
#InstanceFrom ""
Table true
Attribute "Usage"
</Value>
</MBean>
<Connection>
ServiceURL "service:jmx:rmi:///jndi/rmi://localhost:6969/jmxrmi"
Collect "memory_pool"
Collect "memory"
</Connection>
# See /usr/share/doc/collectd/examples/GenericJMX.conf
# for an example config.
</Plugin>
</Plugin>
#<Plugin load>
# ReportRelative true
#</Plugin>
#<Plugin memory>
# ValuesAbsolute true
# ValuesPercentage false
#</Plugin>
#<Plugin processes>
# Process "name"
# ProcessMatch "foobar" "/usr/bin/perl foobar\\.pl.*"
#</Plugin>
#<Plugin swap>
# ReportByDevice false
# ReportBytes true
#</Plugin>
<Plugin write_graphite>
<Node "node">
Host "127.0.0.1"
Port "2003"
Protocol "tcp"
ReconnectInterval 0
LogSendErrors false
Prefix "servers."
StoreRates true
AlwaysAppendDS false
EscapeCharacter "_"
SeparateInstances false
PreserveSeparator false
DropDuplicateFields false
</Node>
</Plugin>

View file

@ -1,75 +0,0 @@
#!/bin/bash
set -e
echo "[*] Bisq Server Monitoring installation script"
##### change paths if necessary for your system
BISQ_REPO_URL=https://raw.githubusercontent.com/bisq-network/bisq
BISQ_REPO_TAG=master
ROOT_USER=root
ROOT_GROUP=root
ROOT_HOME=~root
ROOT_PKG=(nginx collectd openssl)
SYSTEMD_ENV_HOME=/etc/default
#####
echo "[*] Gathering information"
read -p "Please provide the onion address of your service (eg. 3f3cu2yw7u457ztq): " onionaddress
echo "[*] Updating apt repo sources"
sudo -H -i -u "${ROOT_USER}" DEBIAN_FRONTEND=noninteractive apt-get update -q
echo "[*] Upgrading OS packages"
sudo -H -i -u "${ROOT_USER}" DEBIAN_FRONTEND=noninteractive apt-get upgrade -qq -y
echo "[*] Installing base packages"
sudo -H -i -u "${ROOT_USER}" DEBIAN_FRONTEND=noninteractive apt-get install -qq -y ${ROOT_PKG[@]}
echo "[*] Preparing Bisq init script for monitoring"
# remove stuff it it is there already
for file in "${SYSTEMD_ENV_HOME}/bisq.env" "${SYSTEMD_ENV_HOME}/bisq-pricenode.env"
do
if [ -f "$file" ];then
sudo -H -i -u "${ROOT_USER}" sed -i -e 's/-Dcom.sun.management.jmxremote //g' -e 's/-Dcom.sun.management.jmxremote.local.only=true//g' -e 's/ -Dcom.sun.management.jmxremote.host=127.0.0.1//g' -e 's/ -Dcom.sun.management.jmxremote.port=6969//g' -e 's/ -Dcom.sun.management.jmxremote.rmi.port=6969//g' -e 's/ -Dcom.sun.management.jmxremote.ssl=false//g' -e 's/ -Dcom.sun.management.jmxremote.authenticate=false//g' "${file}"
sudo -H -i -u "${ROOT_USER}" sed -i -e '/JAVA_OPTS/ s/"$/ -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=true -Dcom.sun.management.jmxremote.host=127.0.0.1 -Dcom.sun.management.jmxremote.port=6969 -Dcom.sun.management.jmxremote.rmi.port=6969 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"/' "${file}"
fi
done
echo "[*] Seeding entropy from /dev/urandom"
sudo -H -i -u "${ROOT_USER}" /bin/sh -c "head -1500 /dev/urandom > ${ROOT_HOME}/.rnd"
echo "[*] Installing Nginx config"
sudo -H -i -u "${ROOT_USER}" openssl req -x509 -nodes -newkey rsa:2048 -days 3000 -keyout /etc/nginx/cert.key -out /etc/nginx/cert.crt -subj="/O=Bisq/OU=Bisq Infrastructure/CN=$onionaddress"
curl -s "${BISQ_REPO_URL}/${BISQ_REPO_TAG}/monitor/nginx.conf" > /tmp/nginx.conf
sudo -H -i -u "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 /tmp/nginx.conf /etc/nginx/nginx.conf
echo "[*] Installing collectd config"
curl -s "${BISQ_REPO_URL}/${BISQ_REPO_TAG}/monitor/collectd.conf" > /tmp/collectd.conf
sudo -H -i -u "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 /tmp/collectd.conf /etc/collectd/collectd.conf
sudo -H -i -u "${ROOT_USER}" sed -i -e "s/__ONION_ADDRESS__/$onionaddress/" /etc/collectd/collectd.conf
echo "[*] Updating systemd daemon configuration"
sudo -H -i -u "${ROOT_USER}" systemctl daemon-reload
sudo -H -i -u "${ROOT_USER}" systemctl enable nginx.service
sudo -H -i -u "${ROOT_USER}" systemctl enable collectd.service
echo "[*] Restarting services"
set +e
service bisq status >/dev/null 2>&1
[ $? != 4 ] && sudo -H -i -u "${ROOT_USER}" systemctl restart bisq.service
service bisq-pricenode status >/dev/null 2>&1
[ $? != 4 ] && sudo -H -i -u "${ROOT_USER}" systemctl restart bisq-pricenode.service
sudo -H -i -u "${ROOT_USER}" systemctl restart nginx.service
sudo -H -i -u "${ROOT_USER}" systemctl restart collectd.service
echo '[*] Done!'
echo ' '
echo '[*] Report this certificate to the monitoring team!'
echo '----------------------------------------------------------------'
echo "Server: $onionaddress"
echo ' '
cat /etc/nginx/cert.crt
echo '----------------------------------------------------------------'
echo ' '

View file

@ -1,28 +0,0 @@
load_module /usr/lib/nginx/modules/ngx_stream_module.so;
worker_processes 1;
events {
worker_connections 1024;
}
stream {
log_format basic '$remote_addr [$time_local] '
'$protocol Status $status Sent $bytes_sent Received $bytes_received '
'Time $session_time';
error_log syslog:server=unix:/dev/log;
access_log syslog:server=unix:/dev/log basic;
server {
listen 127.0.0.1:2003;
proxy_pass monitor.bisq.network:2002;
proxy_ssl on;
proxy_ssl_certificate /etc/nginx/cert.crt;
proxy_ssl_certificate_key /etc/nginx/cert.key;
proxy_ssl_session_reuse on;
}
}

View file

@ -1,52 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor;
import bisq.network.p2p.network.TorMode;
import org.berndpruenster.netlayer.tor.Tor;
import java.io.File;
/**
* This class uses an already defined Tor via <code>Tor.getDefault()</code>
*
* @author Florian Reimair
*
*/
public class AvailableTor extends TorMode {
private final String hiddenServiceDirectory;
public AvailableTor(File torWorkingDirectory, String hiddenServiceDirectory) {
super(torWorkingDirectory);
this.hiddenServiceDirectory = hiddenServiceDirectory;
}
@Override
public Tor getTor() {
return Tor.getDefault();
}
@Override
public String getHiddenServiceDirectory() {
return hiddenServiceDirectory;
}
}

View file

@ -1,74 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor;
import java.util.Properties;
/**
* Does some pre-computation for a configurable class.
*
* @author Florian Reimair
*/
public abstract class Configurable {
protected Properties configuration = new Properties();
private String name;
/**
* Filters all java properties starting with {@link Configurable#getName()} of
* the class and makes them available. Does <em>NOT</em> parse the content of
* the properties!
* <p>
* For example, if the implementing class sets its name (using
* {@link Configurable#setName(String)}) to <code>MyName</code>, the list of
* properties is scanned for properties starting with <code>MyName</code>.
* Matching lines are made available to the class without the prefix. For
* example, a property <code>MyName.answer=42</code> is made available as
* <code>configuration.getProperty("answer")</code> resulting in
* <code>42</code>.
*
* @param properties a set of configuration properties
*/
public void configure(final Properties properties) {
// only configure the Properties which belong to us
final Properties myProperties = new Properties();
properties.forEach((k, v) -> {
String key = (String) k;
if (key.startsWith(getName()))
myProperties.put(key.substring(key.indexOf(".") + 1), v);
});
// configure all properties that belong to us
this.configuration = myProperties;
}
protected String getName() {
return name;
}
/**
* Set the name used to filter through configuration properties. See
* {@link Configurable#configure(Properties)}.
*
* @param name the name of the configurable
*/
protected void setName(String name) {
this.name = name;
}
}

View file

@ -1,147 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor;
import bisq.common.app.Version;
import bisq.common.util.Utilities;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import static bisq.common.config.Config.BASE_CURRENCY_NETWORK;
/**
* Starts a Metric (in its own {@link Thread}), manages its properties and shuts
* it down gracefully. Furthermore, configuration updates and execution are done
* in a thread-save manner. Implementing classes only have to implement the
* {@link Metric#execute()} method.
*
* @author Florian Reimair
*/
@Slf4j
public abstract class Metric extends Configurable implements Runnable {
private static final String INTERVAL = "run.interval";
private static ScheduledExecutorService executor;
protected final Reporter reporter;
private ScheduledFuture<?> scheduler;
/**
* disable execution
*/
private void disable() {
if (scheduler != null)
scheduler.cancel(false);
}
/**
* enable execution
*/
private void enable() {
scheduler = executor.scheduleWithFixedDelay(this, new Random().nextInt(60),
Long.parseLong(configuration.getProperty(INTERVAL)), TimeUnit.SECONDS);
}
/**
* Constructor.
*/
protected Metric(Reporter reporter) {
this.reporter = reporter;
setName(this.getClass().getSimpleName());
if (executor == null) {
executor = new ScheduledThreadPoolExecutor(6);
}
}
boolean enabled() {
if (scheduler != null)
return !scheduler.isCancelled();
else
return false;
}
@Override
public void configure(final Properties properties) {
synchronized (this) {
log.info("{} (re)loading config...", getName());
super.configure(properties);
reporter.configure(properties);
Version.setBaseCryptoNetworkId(Integer.parseInt(properties.getProperty("System." + BASE_CURRENCY_NETWORK, "1"))); // defaults to BTC_TESTNET
// decide whether to enable or disable the task
if (configuration.isEmpty() || !configuration.getProperty("enabled", "false").equals("true")
|| !configuration.containsKey(INTERVAL)) {
disable();
// some informative log output
if (configuration.isEmpty())
log.error("{} is not configured at all. Will not run.", getName());
else if (!configuration.getProperty("enabled", "false").equals("true"))
log.info("{} is deactivated. Will not run.", getName());
else if (!configuration.containsKey(INTERVAL))
log.error("{} is missing mandatory '" + INTERVAL + "' property. Will not run.", getName());
else
log.error("{} is mis-configured. Will not run.", getName());
} else if (!enabled() && configuration.getProperty("enabled", "false").equals("true")) {
// check if this Metric got activated after being disabled.
// if so, resume execution
enable();
log.info("{} got activated. Starting up.", getName());
}
}
}
@Override
public void run() {
try {
Thread.currentThread().setName("Metric: " + getName());
// execute all the things
synchronized (this) {
log.info("{} started", getName());
execute();
log.info("{} done", getName());
}
} catch (Throwable e) {
log.error("A metric misbehaved!", e);
}
}
/**
* Gets scheduled repeatedly.
*/
protected abstract void execute();
/**
* initiate an orderly shutdown on all metrics. Blocks until all metrics are
* shut down or after one minute.
*/
public static void haltAllMetrics() {
Utilities.shutdownAndAwaitTermination(executor, 2, TimeUnit.MINUTES);
}
}

View file

@ -1,182 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor;
import bisq.monitor.metric.MarketStats;
import bisq.monitor.metric.P2PMarketStats;
import bisq.monitor.metric.P2PNetworkLoad;
import bisq.monitor.metric.P2PRoundTripTime;
import bisq.monitor.metric.P2PSeedNodeSnapshot;
import bisq.monitor.metric.PriceNodeStats;
import bisq.monitor.metric.TorHiddenServiceStartupTime;
import bisq.monitor.metric.TorRoundTripTime;
import bisq.monitor.metric.TorStartupTime;
import bisq.monitor.reporter.ConsoleReporter;
import bisq.monitor.reporter.GraphiteReporter;
import bisq.common.app.Capabilities;
import bisq.common.app.Capability;
import org.berndpruenster.netlayer.tor.NativeTor;
import org.berndpruenster.netlayer.tor.Tor;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkNotNull;
import sun.misc.Signal;
/**
* Monitor executable for the Bisq network.
*
* @author Florian Reimair
*/
@Slf4j
public class Monitor {
public static final File TOR_WORKING_DIR = new File("monitor/work/monitor-tor");
private static String[] args = {};
public static void main(String[] args) throws Throwable {
Monitor.args = args;
new Monitor().start();
}
/**
* A list of all active {@link Metric}s
*/
private final List<Metric> metrics = new ArrayList<>();
/**
* Starts up all configured Metrics.
*
* @throws Throwable in case something goes wrong
*/
private void start() throws Throwable {
// start Tor
Tor.setDefault(new NativeTor(TOR_WORKING_DIR, null, null, false));
//noinspection deprecation,deprecation,deprecation,deprecation,deprecation,deprecation,deprecation,deprecation
Capabilities.app.addAll(Capability.TRADE_STATISTICS,
Capability.TRADE_STATISTICS_2,
Capability.ACCOUNT_AGE_WITNESS,
Capability.ACK_MSG,
Capability.PROPOSAL,
Capability.BLIND_VOTE,
Capability.DAO_STATE,
Capability.BUNDLE_OF_ENVELOPES,
Capability.REFUND_AGENT,
Capability.MEDIATION,
Capability.TRADE_STATISTICS_3);
// assemble Metrics
// - create reporters
Reporter graphiteReporter = new GraphiteReporter();
// only use ConsoleReporter if requested (for debugging for example)
Properties properties = getProperties();
if ("true".equals(properties.getProperty("System.useConsoleReporter", "false")))
graphiteReporter = new ConsoleReporter();
// - add available metrics with their reporters
metrics.add(new TorStartupTime(graphiteReporter));
metrics.add(new TorRoundTripTime(graphiteReporter));
metrics.add(new TorHiddenServiceStartupTime(graphiteReporter));
metrics.add(new P2PRoundTripTime(graphiteReporter));
metrics.add(new P2PNetworkLoad(graphiteReporter));
metrics.add(new P2PSeedNodeSnapshot(graphiteReporter));
metrics.add(new P2PMarketStats(graphiteReporter));
metrics.add(new PriceNodeStats(graphiteReporter));
metrics.add(new MarketStats(graphiteReporter));
// prepare configuration reload
// Note that this is most likely only work on Linux
Signal.handle(new Signal("USR1"), signal -> {
try {
configure();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
// configure Metrics
// - which also starts the metrics if appropriate
configure();
// exit Metrics gracefully on shutdown
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
// set the name of the Thread for debugging purposes
log.info("system shutdown initiated");
log.info("shutting down active metrics...");
Metric.haltAllMetrics();
try {
log.info("shutting down tor...");
Tor tor = Tor.getDefault();
checkNotNull(tor, "tor must not be null");
tor.shutdown();
} catch (Throwable ignore) {
}
log.info("system halt");
}, "Monitor Shutdown Hook ")
);
}
/**
* Reload the configuration from disk.
*
* @throws Exception if something goes wrong
*/
private void configure() throws Exception {
Properties properties = getProperties();
for (Metric current : metrics)
current.configure(properties);
}
/**
* Overloads a default set of properties with a file if given
*
* @return a set of properties
* @throws Exception in case something goes wrong
*/
private Properties getProperties() throws Exception {
Properties result = new Properties();
// if we have a config file load the config file, else, load the default config
// from the resources
if (args.length > 0)
result.load(new FileInputStream(args[0]));
else
result.load(Monitor.class.getClassLoader().getResourceAsStream("metrics.properties"));
return result;
}
}

View file

@ -1,47 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor;
import bisq.network.p2p.NodeAddress;
import java.net.MalformedURLException;
import java.net.URL;
/**
* Helper for parsing and pretty printing onion addresses.
*
* @author Florian Reimair
*/
public class OnionParser {
public static NodeAddress getNodeAddress(final String current) throws MalformedURLException {
String nodeAddress = current.trim();
if (!nodeAddress.startsWith("http://"))
nodeAddress = "http://" + nodeAddress;
URL tmp = new URL(nodeAddress);
return new NodeAddress(tmp.getHost(), tmp.getPort() > 0 ? tmp.getPort() : 80);
}
public static String prettyPrint(final NodeAddress host) {
return host.getHostNameWithoutPostFix();
}
public static String prettyPrint(String host) throws MalformedURLException {
return prettyPrint(getNodeAddress(host));
}
}

View file

@ -1,74 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor;
import java.util.Map;
/**
* Reports findings to a specific service/file/place using the proper means to
* do so.
*
* @author Florian Reimair
*/
public abstract class Reporter extends Configurable {
protected Reporter() {
setName(this.getClass().getSimpleName());
}
/**
* Report our findings.
*
* @param value the value to report
*/
public abstract void report(long value);
/**
* Report our findings
*
* @param value the value to report
* @param prefix a common prefix to be included in the tag name
*/
public abstract void report(long value, String prefix);
/**
* Report our findings.
*
* @param values Map<metric name, metric value>
*/
public abstract void report(Map<String, String> values);
/**
* Report our findings.
*
* @param values Map<metric name, metric value>
* @param prefix for example "torStartupTime"
*/
public abstract void report(Map<String, String> values, String prefix);
/**
* Report our findings one by one.
*
* @param key the metric name
* @param value the value to report
* @param timestamp a unix timestamp in milliseconds
* @param prefix for example "torStartupTime"
*/
public abstract void report(String key, String value, String timestamp, String prefix);
}

View file

@ -1,70 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.LongSummaryStatistics;
import java.util.Map;
/**
* Calculates average, max, min, p25, p50, p75 off of a list of samples and
* throws in the sample size for good measure.
*
* @author Florian Reimair
*/
public class StatisticsHelper {
public static Map<String, String> process(Collection<Long> input) {
List<Long> samples = new ArrayList<>(input);
// aftermath
Collections.sort(samples);
// - average, max, min , sample size
LongSummaryStatistics statistics = samples.stream().mapToLong(val -> val).summaryStatistics();
Map<String, String> results = new HashMap<>();
results.put("average", String.valueOf(Math.round(statistics.getAverage())));
results.put("max", String.valueOf(statistics.getMax()));
results.put("min", String.valueOf(statistics.getMin()));
results.put("sampleSize", String.valueOf(statistics.getCount()));
// - p25, median, p75
Integer[] percentiles = new Integer[] { 25, 50, 75 };
for (Integer percentile : percentiles) {
double rank = statistics.getCount() * percentile / 100.0;
Long percentileValue;
if (samples.size() <= rank + 1)
percentileValue = samples.get(samples.size() - 1);
else if (Math.floor(rank) == rank)
percentileValue = samples.get((int) rank);
else
percentileValue = Math.round(samples.get((int) Math.floor(rank))
+ (samples.get((int) (Math.floor(rank) + 1)) - samples.get((int) Math.floor(rank)))
/ (rank - Math.floor(rank)));
results.put("p" + percentile, String.valueOf(percentileValue));
}
return results;
}
}

View file

@ -1,81 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
/**
* Gate pattern to help with thread synchronization
*
* @author Florian Reimair
*/
@Slf4j
public class ThreadGate {
private CountDownLatch lock = new CountDownLatch(0);
/**
* Make everyone wait until the gate is open again.
*/
public void engage() {
lock = new CountDownLatch(1);
}
/**
* Make everyone wait until the gate is open again.
*
* @param numberOfLocks how often the gate has to be unlocked until the gate
* opens.
*/
public void engage(int numberOfLocks) {
lock = new CountDownLatch(numberOfLocks);
}
/**
* Wait for the gate to be opened. Blocks until the gate is open again. Returns
* immediately if the gate is already open.
*/
public synchronized void await() {
while (lock.getCount() > 0)
try {
if (!lock.await(60, TimeUnit.SECONDS)) {
log.warn("timeout occurred!");
break; // break the loop
}
} catch (InterruptedException ignore) {
}
}
/**
* Open the gate and let everyone proceed with their execution.
*/
public void proceed() {
lock.countDown();
}
/**
* Open the gate with no regards on how many locks are still in place.
*/
public void unlock() {
while (lock.getCount() > 0)
lock.countDown();
}
}

View file

@ -1,100 +0,0 @@
/*
* This file is part of Bisq.
*
* bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor.metric;
import bisq.monitor.Metric;
import bisq.monitor.Reporter;
import java.net.URL;
import java.net.URLConnection;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.extern.slf4j.Slf4j;
/**
* Uses the markets API to retrieve market volume data.
*
* @author Florian Reimair
*
*/
@Slf4j
public class MarketStats extends Metric {
private static final String MARKETS_BISQ_NETWORK = "https://markets.bisq.network";
// poor mans JSON parser
private final Pattern marketPattern = Pattern.compile("\"market\" ?: ?\"([a-z_]+)\"");
private final Pattern amountPattern = Pattern.compile("\"amount\" ?: ?\"([\\d\\.]+)\"");
private final Pattern volumePattern = Pattern.compile("\"volume\" ?: ?\"([\\d\\.]+)\"");
private final Pattern timestampPattern = Pattern.compile("\"trade_date\" ?: ?([\\d]+)");
private Long lastRun = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(15));
public MarketStats(Reporter reporter) {
super(reporter);
}
@Override
protected void execute() {
try {
// for each configured host
Map<String, String> result = new HashMap<>();
// assemble query
long now = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
String query = "/api/trades?format=json&market=all&timestamp_from=" + lastRun + "&timestamp_to=" + now;
lastRun = now; // thought about adding 1 second but what if a trade is done exactly in this one second?
// connect
URLConnection connection = new URL(MARKETS_BISQ_NETWORK + query).openConnection();
// prepare to receive data
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line, all = "";
while ((line = in.readLine()) != null)
all += ' ' + line;
in.close();
Arrays.stream(all.substring(0, all.length() - 2).split("}")).forEach(trade -> {
Matcher market = marketPattern.matcher(trade);
Matcher amount = amountPattern.matcher(trade);
Matcher timestamp = timestampPattern.matcher(trade);
market.find();
if (market.group(1).endsWith("btc")) {
amount = volumePattern.matcher(trade);
}
amount.find();
timestamp.find();
reporter.report("volume." + market.group(1), amount.group(1), timestamp.group(1), getName());
});
} catch (IllegalStateException ignore) {
// no match found
} catch (IOException e) {
e.printStackTrace();
}
}
}

View file

@ -1,282 +0,0 @@
/*
* This file is part of Bisq.
*
* bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor.metric;
import bisq.monitor.Reporter;
import bisq.core.offer.OfferUtil;
import bisq.core.offer.bisq_v1.OfferPayload;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.network.Connection;
import bisq.network.p2p.peers.getdata.messages.GetDataResponse;
import bisq.network.p2p.peers.getdata.messages.PreliminaryGetDataRequest;
import bisq.network.p2p.storage.payload.ProtectedStorageEntry;
import bisq.network.p2p.storage.payload.ProtectedStoragePayload;
import bisq.common.proto.network.NetworkEnvelope;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Demo Stats metric derived from the OfferPayload messages we get from the seed nodes
*
* @author Florian Reimair
*/
@Slf4j
public class P2PMarketStats extends P2PSeedNodeSnapshotBase {
final Map<NodeAddress, Statistics<Aggregator>> versionBucketsPerHost = new ConcurrentHashMap<>();
final Map<NodeAddress, Statistics<Aggregator>> offerVolumeBucketsPerHost = new ConcurrentHashMap<>();
final Map<NodeAddress, Statistics<List<Long>>> offerVolumeDistributionBucketsPerHost = new ConcurrentHashMap<>();
final Map<NodeAddress, Statistics<Map<NodeAddress, Aggregator>>> offersPerTraderBucketsPerHost = new ConcurrentHashMap<>();
final Map<NodeAddress, Statistics<Map<NodeAddress, Aggregator>>> volumePerTraderBucketsPerHost = new ConcurrentHashMap<>();
/**
* Efficient way to aggregate numbers.
*/
private static class Aggregator {
private long value = 0;
synchronized long value() {
return value;
}
synchronized void increment() {
value++;
}
synchronized void add(long amount) {
value += amount;
}
}
private abstract static class OfferStatistics<T> extends Statistics<T> {
@Override
public synchronized void log(Object message) {
if (message instanceof OfferPayload) {
OfferPayload currentMessage = (OfferPayload) message;
// For logging different data types
String market = currentMessage.getDirection() + "." + currentMessage.getBaseCurrencyCode() + "_" + currentMessage.getCounterCurrencyCode();
process(market, currentMessage);
}
}
abstract void process(String market, OfferPayload currentMessage);
}
private class OfferCountStatistics extends OfferStatistics<Aggregator> {
@Override
void process(String market, OfferPayload currentMessage) {
buckets.putIfAbsent(market, new Aggregator());
buckets.get(market).increment();
}
}
private class OfferVolumeStatistics extends OfferStatistics<Aggregator> {
@Override
void process(String market, OfferPayload currentMessage) {
buckets.putIfAbsent(market, new Aggregator());
buckets.get(market).add(currentMessage.getAmount());
}
}
private class OfferVolumeDistributionStatistics extends OfferStatistics<List<Long>> {
@Override
void process(String market, OfferPayload currentMessage) {
buckets.putIfAbsent(market, new ArrayList<>());
buckets.get(market).add(currentMessage.getAmount());
}
}
private class OffersPerTraderStatistics extends OfferStatistics<Map<NodeAddress, Aggregator>> {
@Override
void process(String market, OfferPayload currentMessage) {
buckets.putIfAbsent(market, new HashMap<>());
buckets.get(market).putIfAbsent(currentMessage.getOwnerNodeAddress(), new Aggregator());
buckets.get(market).get(currentMessage.getOwnerNodeAddress()).increment();
}
}
private class VolumePerTraderStatistics extends OfferStatistics<Map<NodeAddress, Aggregator>> {
@Override
void process(String market, OfferPayload currentMessage) {
buckets.putIfAbsent(market, new HashMap<>());
buckets.get(market).putIfAbsent(currentMessage.getOwnerNodeAddress(), new Aggregator());
buckets.get(market).get(currentMessage.getOwnerNodeAddress()).add(currentMessage.getAmount());
}
}
private class VersionsStatistics extends Statistics<Aggregator> {
@Override
public void log(Object message) {
if (message instanceof OfferPayload) {
OfferPayload offerPayload = (OfferPayload) message;
String version = "v" + OfferUtil.getVersionFromId(offerPayload.getId());
buckets.putIfAbsent(version, new Aggregator());
buckets.get(version).increment();
}
}
}
public P2PMarketStats(Reporter graphiteReporter) {
super(graphiteReporter);
}
@Override
protected List<NetworkEnvelope> getRequests() {
List<NetworkEnvelope> result = new ArrayList<>();
Random random = new Random();
result.add(new PreliminaryGetDataRequest(random.nextInt(), hashes));
return result;
}
protected void createHistogram(List<Long> input, String market, Map<String, String> report) {
int numberOfBins = 5;
// - get biggest offer
double max = input.stream().max(Long::compareTo).map(value -> value * 1.01).orElse(0.0);
// - create histogram
input.stream().collect(
Collectors.groupingBy(aLong -> aLong == max ? numberOfBins - 1 : (int) Math.floor(aLong / (max / numberOfBins)), Collectors.counting())).
forEach((integer, integer2) -> report.put(market + ".bin_" + integer, String.valueOf(integer2)));
report.put(market + ".number_of_bins", String.valueOf(numberOfBins));
report.put(market + ".max", String.valueOf((int) max));
}
@Override
protected void report() {
Map<String, String> report = new HashMap<>();
bucketsPerHost.values().stream().findFirst().ifPresent(nodeAddressStatisticsEntry -> nodeAddressStatisticsEntry.values().forEach((market, numberOfOffers) -> report.put(market, String.valueOf(((Aggregator) numberOfOffers).value()))));
reporter.report(report, getName() + ".offerCount");
// do offerbook volume statistics
report.clear();
offerVolumeBucketsPerHost.values().stream().findFirst().ifPresent(aggregatorStatistics -> aggregatorStatistics.values().forEach((market, numberOfOffers) -> report.put(market, String.valueOf(numberOfOffers.value()))));
reporter.report(report, getName() + ".volume");
// do the offer vs volume histogram
report.clear();
// - get a data set
offerVolumeDistributionBucketsPerHost.values().stream().findFirst().ifPresent(listStatistics -> listStatistics.values().forEach((market, offers) -> {
createHistogram(offers, market, report);
}));
reporter.report(report, getName() + ".volume-per-offer-distribution");
// do offers per trader
report.clear();
// - get a data set
offersPerTraderBucketsPerHost.values().stream().findFirst().ifPresent(mapStatistics -> mapStatistics.values().forEach((market, stuff) -> {
List<Long> offerPerTrader = stuff.values().stream().map(Aggregator::value).collect(Collectors.toList());
createHistogram(offerPerTrader, market, report);
}));
reporter.report(report, getName() + ".traders_by_number_of_offers");
// do volume per trader
report.clear();
// - get a data set
volumePerTraderBucketsPerHost.values().stream().findFirst().ifPresent(mapStatistics -> mapStatistics.values().forEach((market, stuff) -> {
List<Long> volumePerTrader = stuff.values().stream().map(Aggregator::value).collect(Collectors.toList());
createHistogram(volumePerTrader, market, report);
}));
reporter.report(report, getName() + ".traders_by_volume");
// do version statistics
report.clear();
Optional<Statistics<Aggregator>> optionalStatistics = versionBucketsPerHost.values().stream().findAny();
optionalStatistics.ifPresent(aggregatorStatistics -> aggregatorStatistics.values()
.forEach((version, numberOfOccurrences) -> report.put(version, String.valueOf(numberOfOccurrences.value()))));
reporter.report(report, "versions");
}
protected boolean treatMessage(NetworkEnvelope networkEnvelope, Connection connection) {
checkNotNull(connection.getPeersNodeAddressProperty(),
"although the property is nullable, we need it to not be null");
if (networkEnvelope instanceof GetDataResponse) {
Statistics offerCount = new OfferCountStatistics();
Statistics offerVolume = new OfferVolumeStatistics();
Statistics offerVolumeDistribution = new OfferVolumeDistributionStatistics();
Statistics offersPerTrader = new OffersPerTraderStatistics();
Statistics volumePerTrader = new VolumePerTraderStatistics();
Statistics versions = new VersionsStatistics();
GetDataResponse dataResponse = (GetDataResponse) networkEnvelope;
final Set<ProtectedStorageEntry> dataSet = dataResponse.getDataSet();
dataSet.forEach(e -> {
final ProtectedStoragePayload protectedStoragePayload = e.getProtectedStoragePayload();
if (protectedStoragePayload == null) {
log.warn("StoragePayload was null: {}", networkEnvelope.toString());
return;
}
offerCount.log(protectedStoragePayload);
offerVolume.log(protectedStoragePayload);
offerVolumeDistribution.log(protectedStoragePayload);
offersPerTrader.log(protectedStoragePayload);
volumePerTrader.log(protectedStoragePayload);
versions.log(protectedStoragePayload);
});
dataResponse.getPersistableNetworkPayloadSet().forEach(persistableNetworkPayload -> {
// memorize message hashes
//Byte[] bytes = new Byte[persistableNetworkPayload.getHash().length];
//Arrays.setAll(bytes, n -> persistableNetworkPayload.getHash()[n]);
//hashes.add(bytes);
hashes.add(persistableNetworkPayload.getHash());
});
bucketsPerHost.put(connection.getPeersNodeAddressProperty().getValue(), offerCount);
offerVolumeBucketsPerHost.put(connection.getPeersNodeAddressProperty().getValue(), offerVolume);
offerVolumeDistributionBucketsPerHost.put(connection.getPeersNodeAddressProperty().getValue(), offerVolumeDistribution);
offersPerTraderBucketsPerHost.put(connection.getPeersNodeAddressProperty().getValue(), offersPerTrader);
volumePerTraderBucketsPerHost.put(connection.getPeersNodeAddressProperty().getValue(), volumePerTrader);
versionBucketsPerHost.put(connection.getPeersNodeAddressProperty().getValue(), versions);
return true;
}
return false;
}
}

View file

@ -1,245 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor.metric;
import bisq.monitor.AvailableTor;
import bisq.monitor.Metric;
import bisq.monitor.Monitor;
import bisq.monitor.Reporter;
import bisq.monitor.ThreadGate;
import bisq.core.network.p2p.seed.DefaultSeedNodeRepository;
import bisq.core.proto.network.CoreNetworkProtoResolver;
import bisq.core.proto.persistable.CorePersistenceProtoResolver;
import bisq.network.p2p.network.Connection;
import bisq.network.p2p.network.MessageListener;
import bisq.network.p2p.network.NetworkNode;
import bisq.network.p2p.network.SetupListener;
import bisq.network.p2p.network.TorNetworkNode;
import bisq.network.p2p.peers.PeerManager;
import bisq.network.p2p.peers.keepalive.KeepAliveManager;
import bisq.network.p2p.peers.peerexchange.PeerExchangeManager;
import bisq.network.p2p.storage.messages.BroadcastMessage;
import bisq.common.ClockWatcher;
import bisq.common.config.Config;
import bisq.common.file.CorruptedStorageFileHandler;
import bisq.common.persistence.PersistenceManager;
import bisq.common.proto.network.NetworkEnvelope;
import bisq.common.proto.network.NetworkProtoResolver;
import java.time.Clock;
import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import lombok.extern.slf4j.Slf4j;
/**
* Contacts a list of hosts and asks them for all the data we do not have. The
* answers are then compiled into buckets of message types. Based on these
* buckets, the Metric reports (for each host) the message types observed and
* their number along with a relative comparison between all hosts.
*
* @author Florian Reimair
*
*/
@Slf4j
public class P2PNetworkLoad extends Metric implements MessageListener, SetupListener {
private static final String TOR_PROXY_PORT = "run.torProxyPort";
private static final String MAX_CONNECTIONS = "run.maxConnections";
private static final String HISTORY_SIZE = "run.historySize";
private NetworkNode networkNode;
private final File torHiddenServiceDir = new File("metric_" + getName());
private final ThreadGate hsReady = new ThreadGate();
private final Map<String, Counter> buckets = new ConcurrentHashMap<>();
/**
* Buffers the last X message we received. New messages will only be logged in case
* the message isn't already in the history. Note that the oldest message hashes are
* dropped to record newer hashes.
*/
private Map<Integer, Object> history;
private long lastRun = 0;
/**
* History implementation using a {@link LinkedHashMap} and its
* {@link LinkedHashMap#removeEldestEntry(Map.Entry)} option.
*/
private static class FixedSizeHistoryTracker<K, V> extends LinkedHashMap<K, V> {
final int historySize;
FixedSizeHistoryTracker(int historySize) {
super(historySize, 10, true);
this.historySize = historySize;
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > historySize;
}
}
@Override
protected void execute() {
// in case we do not have a NetworkNode up and running, we create one
if (null == networkNode) {
// prepare the gate
hsReady.engage();
// start the network node
networkNode = new TorNetworkNode(Integer.parseInt(configuration.getProperty(TOR_PROXY_PORT, "9053")),
new CoreNetworkProtoResolver(Clock.systemDefaultZone()), false,
new AvailableTor(Monitor.TOR_WORKING_DIR, torHiddenServiceDir.getName()), null);
networkNode.start(this);
// wait for the HS to be published
hsReady.await();
// boot up P2P node
try {
Config config = new Config();
CorruptedStorageFileHandler corruptedStorageFileHandler = new CorruptedStorageFileHandler();
int maxConnections = Integer.parseInt(configuration.getProperty(MAX_CONNECTIONS, "12"));
NetworkProtoResolver networkProtoResolver = new CoreNetworkProtoResolver(Clock.systemDefaultZone());
CorePersistenceProtoResolver persistenceProtoResolver = new CorePersistenceProtoResolver(null,
networkProtoResolver);
DefaultSeedNodeRepository seedNodeRepository = new DefaultSeedNodeRepository(config);
PeerManager peerManager = new PeerManager(networkNode, seedNodeRepository, new ClockWatcher(),
new PersistenceManager<>(torHiddenServiceDir, persistenceProtoResolver, corruptedStorageFileHandler), maxConnections);
// init file storage
peerManager.readPersisted(() -> {
});
PeerExchangeManager peerExchangeManager = new PeerExchangeManager(networkNode, seedNodeRepository,
peerManager);
// updates the peer list every now and then as well
peerExchangeManager
.requestReportedPeersFromSeedNodes(seedNodeRepository.getSeedNodeAddresses().iterator().next());
KeepAliveManager keepAliveManager = new KeepAliveManager(networkNode, peerManager);
keepAliveManager.start();
networkNode.addMessageListener(this);
} catch (Throwable e) {
e.printStackTrace();
}
}
// report
Map<String, String> report = new HashMap<>();
if (lastRun != 0 && System.currentTimeMillis() - lastRun != 0) {
// - normalize to data/minute
double perMinuteFactor = 60000.0 / (System.currentTimeMillis() - lastRun);
// - get snapshot so we do not loose data
Set<String> keys = new HashSet<>(buckets.keySet());
// - transfer values to report
keys.forEach(key -> {
int value = buckets.get(key).getAndReset();
if (value != 0) {
report.put(key, String.format("%.2f", value * perMinuteFactor));
}
});
// - report
reporter.report(report, getName());
}
// - reset last run
lastRun = System.currentTimeMillis();
}
public P2PNetworkLoad(Reporter reporter) {
super(reporter);
}
@Override
public void configure(Properties properties) {
super.configure(properties);
history = Collections.synchronizedMap(new FixedSizeHistoryTracker<>(Integer.parseInt(configuration.getProperty(HISTORY_SIZE, "200"))));
}
/**
* Efficient way to count message occurrences.
*/
private static class Counter {
private int value = 1;
synchronized int getAndReset() {
try {
return value;
} finally {
value = 0;
}
}
synchronized void increment() {
value++;
}
}
@Override
public void onMessage(NetworkEnvelope networkEnvelope, Connection connection) {
if (networkEnvelope instanceof BroadcastMessage) {
try {
if (history.get(networkEnvelope.hashCode()) == null) {
history.put(networkEnvelope.hashCode(), null);
buckets.get(networkEnvelope.getClass().getSimpleName()).increment();
}
} catch (NullPointerException e) {
// use exception handling because we hardly ever need to add a fresh bucket
buckets.put(networkEnvelope.getClass().getSimpleName(), new Counter());
}
}
}
@Override
public void onTorNodeReady() {
}
@Override
public void onHiddenServicePublished() {
// open the gate
hsReady.proceed();
}
@Override
public void onSetupFailed(Throwable throwable) {
}
@Override
public void onRequestCustomBridges() {
}
}

View file

@ -1,111 +0,0 @@
/*
* This file is part of Bisq.
*
* bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor.metric;
import bisq.monitor.OnionParser;
import bisq.monitor.Reporter;
import bisq.monitor.StatisticsHelper;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.network.CloseConnectionReason;
import bisq.network.p2p.network.Connection;
import bisq.network.p2p.peers.keepalive.messages.Ping;
import bisq.network.p2p.peers.keepalive.messages.Pong;
import bisq.common.proto.network.NetworkEnvelope;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import static com.google.common.base.Preconditions.checkNotNull;
public class P2PRoundTripTime extends P2PSeedNodeSnapshotBase {
private static final String SAMPLE_SIZE = "run.sampleSize";
private final Map<Integer, Long> sentAt = new HashMap<>();
private Map<NodeAddress, Statistics> measurements = new HashMap<>();
public P2PRoundTripTime(Reporter reporter) {
super(reporter);
}
/**
* Use a counter to do statistics.
*/
private class Statistics {
private final List<Long> samples = new ArrayList<>();
public synchronized void log(Object message) {
Pong pong = (Pong) message;
Long start = sentAt.get(pong.getRequestNonce());
if (start != null)
samples.add(System.currentTimeMillis() - start);
}
public List<Long> values() {
return samples;
}
}
@Override
protected List<NetworkEnvelope> getRequests() {
List<NetworkEnvelope> result = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < Integer.parseInt(configuration.getProperty(SAMPLE_SIZE, "1")); i++)
result.add(new Ping(random.nextInt(), 42));
return result;
}
@Override
protected void aboutToSend(NetworkEnvelope message) {
sentAt.put(((Ping) message).getNonce(), System.currentTimeMillis());
}
@Override
protected boolean treatMessage(NetworkEnvelope networkEnvelope, Connection connection) {
if (networkEnvelope instanceof Pong) {
checkNotNull(connection.getPeersNodeAddressProperty(),
"although the property is nullable, we need it to not be null");
measurements.putIfAbsent(connection.getPeersNodeAddressProperty().getValue(), new Statistics());
measurements.get(connection.getPeersNodeAddressProperty().getValue()).log(networkEnvelope);
connection.shutDown(CloseConnectionReason.APP_SHUT_DOWN);
return true;
}
return false;
}
@Override
void report() {
// report
measurements.forEach(((nodeAddress, samples) ->
reporter.report(StatisticsHelper.process(samples.values()),
getName() + "." + OnionParser.prettyPrint(nodeAddress))
));
// clean up for next round
measurements = new HashMap<>();
}
}

View file

@ -1,292 +0,0 @@
/*
* This file is part of Bisq.
*
* bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor.metric;
import bisq.monitor.OnionParser;
import bisq.monitor.Reporter;
import bisq.core.dao.monitoring.model.StateHash;
import bisq.core.dao.monitoring.network.messages.GetBlindVoteStateHashesRequest;
import bisq.core.dao.monitoring.network.messages.GetDaoStateHashesRequest;
import bisq.core.dao.monitoring.network.messages.GetProposalStateHashesRequest;
import bisq.core.dao.monitoring.network.messages.GetStateHashesResponse;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.network.Connection;
import bisq.network.p2p.peers.getdata.messages.GetDataResponse;
import bisq.network.p2p.peers.getdata.messages.PreliminaryGetDataRequest;
import bisq.network.p2p.storage.payload.ProtectedStorageEntry;
import bisq.network.p2p.storage.payload.ProtectedStoragePayload;
import bisq.common.proto.network.NetworkEnvelope;
import java.net.MalformedURLException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Contacts a list of hosts and asks them for all the data excluding persisted messages. The
* answers are then compiled into buckets of message types. Based on these
* buckets, the Metric reports (for each host) the message types observed and
* their number.
*
* Furthermore, since the DAO is a thing now, the consistency of the DAO state held by each host is assessed and reported.
*
* @author Florian Reimair
*
*/
@Slf4j
public class P2PSeedNodeSnapshot extends P2PSeedNodeSnapshotBase {
final Map<NodeAddress, Statistics<Set<Integer>>> bucketsPerHost = new ConcurrentHashMap<>();
private int daostateheight = 594000;
private int proposalheight = daostateheight;
private int blindvoteheight = daostateheight;
/**
* Use a counter to do statistics.
*/
private static class MyStatistics extends Statistics<Set<Integer>> {
@Override
public synchronized void log(Object message) {
// For logging different data types
String className = message.getClass().getSimpleName();
buckets.putIfAbsent(className, new HashSet<>());
buckets.get(className).add(message.hashCode());
}
}
public P2PSeedNodeSnapshot(Reporter reporter) {
super(reporter);
}
protected List<NetworkEnvelope> getRequests() {
List<NetworkEnvelope> result = new ArrayList<>();
Random random = new Random();
result.add(new PreliminaryGetDataRequest(random.nextInt(), hashes));
result.add(new GetDaoStateHashesRequest(daostateheight, random.nextInt()));
result.add(new GetProposalStateHashesRequest(proposalheight, random.nextInt()));
result.add(new GetBlindVoteStateHashesRequest(blindvoteheight, random.nextInt()));
return result;
}
/**
* Report all the stuff. Uses the configured reporter directly.
*/
void report() {
// report
Map<String, String> report = new HashMap<>();
// - assemble histograms
bucketsPerHost.forEach((host, statistics) -> statistics.values().forEach((type, set) -> report
.put(OnionParser.prettyPrint(host) + ".numberOfMessages." + type, Integer.toString(set.size()))));
// - assemble diffs
// - transfer values
Map<String, Statistics<Set<Integer>>> messagesPerHost = new HashMap<>();
bucketsPerHost.forEach((host, value) -> messagesPerHost.put(OnionParser.prettyPrint(host), value));
// - pick reference seed node and its values
String referenceHost = "overall_number_of_unique_messages";
Map<String, Set<Object>> referenceValues = new HashMap<>();
messagesPerHost.forEach((host, statistics) -> statistics.values().forEach((type, set) -> {
referenceValues.putIfAbsent(type, new HashSet<>());
referenceValues.get(type).addAll(set);
}));
// - calculate diffs
messagesPerHost.forEach(
(host, statistics) -> {
statistics.values().forEach((messageType, set) -> {
try {
report.put(OnionParser.prettyPrint(host) + ".relativeNumberOfMessages." + messageType,
String.valueOf(set.size() - referenceValues.get(messageType).size()));
} catch (MalformedURLException | NullPointerException e) {
log.error("we should never have gotten here", e);
}
});
try {
report.put(OnionParser.prettyPrint(host) + ".referenceHost", referenceHost);
} catch (MalformedURLException ignore) {
log.error("we should never got here");
}
});
// cleanup for next run
bucketsPerHost.forEach((host, statistics) -> statistics.reset());
// when our hash cache exceeds a hard limit, we clear the cache and start anew
if (hashes.size() > 150000)
hashes.clear();
// - report
reporter.report(report, getName());
// - assemble dao report
Map<String, String> daoreport = new HashMap<>();
// - transcode
Map<String, Map<NodeAddress, Tuple>> perType = new HashMap<>();
daoData.forEach((nodeAddress, daostatistics) -> daostatistics.values().forEach((type, tuple) -> {
perType.putIfAbsent(type, new HashMap<>());
perType.get(type).put(nodeAddress, tuple);
}));
// - process dao data
perType.forEach((type, nodeAddressTupleMap) -> {
// - find head
int head = nodeAddressTupleMap.values().stream().max(Comparator.comparingLong(Tuple::getHeight))
.map(value -> (int) value.height)
.orElse(0);
int oldest = nodeAddressTupleMap.values().stream().min(Comparator.comparingLong(Tuple::getHeight))
.map(value -> (int) value.height)
.orElse(0);
// - update queried height
if (type.contains("DaoState"))
daostateheight = oldest - 20;
else if (type.contains("Proposal"))
proposalheight = oldest - 20;
else
blindvoteheight = oldest - 20;
// - calculate diffs
nodeAddressTupleMap.forEach((nodeAddress, tuple) -> daoreport.put(type + "." + OnionParser.prettyPrint(nodeAddress) + ".head", Long.toString(tuple.height - head)));
// - memorize hashes
Map<ByteBuffer, Integer> hitcount = new HashMap<>();
nodeAddressTupleMap.forEach((nodeAddress, tuple) -> {
ByteBuffer hash = ByteBuffer.wrap(tuple.hash);
if (hitcount.containsKey(hash)) {
hitcount.put(hash, hitcount.get(hash) + 1);
} else
hitcount.put(hash, 1);
});
hitcount.clear();
nodeAddressTupleMap.forEach((nodeAddress, tuple) ->
daoreport.put(type + "." + OnionParser.prettyPrint(nodeAddress) + ".hash",
Integer.toString(Arrays.asList(hitcount.entrySet().stream()
.sorted((o1, o2) -> o2.getValue().compareTo(o1.getValue()))
.map(Map.Entry::getKey).toArray()).indexOf(ByteBuffer
.wrap(tuple.hash)))));
// - report reference head
daoreport.put(type + ".referenceHead", Integer.toString(head));
});
daoData.clear();
// - report
reporter.report(daoreport, "DaoStateSnapshot");
}
private static class Tuple {
@Getter
private final long height;
private final byte[] hash;
Tuple(long height, byte[] hash) {
this.height = height;
this.hash = hash;
}
}
private class DaoStatistics extends Statistics<Tuple> {
@Override
public void log(Object message) {
// get last entry
StateHash last = (StateHash) ((GetStateHashesResponse) message).getStateHashes().get(((GetStateHashesResponse) message).getStateHashes().size() - 1);
// For logging different data types
String className = last.getClass().getSimpleName();
buckets.putIfAbsent(className, new Tuple(last.getHeight(), last.getHash()));
}
}
private final Map<NodeAddress, Statistics<Tuple>> daoData = new ConcurrentHashMap<>();
protected boolean treatMessage(NetworkEnvelope networkEnvelope, Connection connection) {
checkNotNull(connection.getPeersNodeAddressProperty(),
"although the property is nullable, we need it to not be null");
if (networkEnvelope instanceof GetDataResponse) {
Statistics result = new MyStatistics();
GetDataResponse dataResponse = (GetDataResponse) networkEnvelope;
final Set<ProtectedStorageEntry> dataSet = dataResponse.getDataSet();
dataSet.forEach(e -> {
final ProtectedStoragePayload protectedStoragePayload = e.getProtectedStoragePayload();
if (protectedStoragePayload == null) {
log.warn("StoragePayload was null: {}", networkEnvelope.toString());
return;
}
result.log(protectedStoragePayload);
});
dataResponse.getPersistableNetworkPayloadSet().forEach(persistableNetworkPayload -> {
// memorize message hashes
//Byte[] bytes = new Byte[persistableNetworkPayload.getHash().length];
//Arrays.setAll(bytes, n -> persistableNetworkPayload.getHash()[n]);
//hashes.add(bytes);
hashes.add(persistableNetworkPayload.getHash());
});
bucketsPerHost.put(connection.getPeersNodeAddressProperty().getValue(), result);
return true;
} else if (networkEnvelope instanceof GetStateHashesResponse) {
daoData.putIfAbsent(connection.getPeersNodeAddressProperty().getValue(), new DaoStatistics());
daoData.get(connection.getPeersNodeAddressProperty().getValue()).log(networkEnvelope);
return true;
}
return false;
}
}

View file

@ -1,241 +0,0 @@
/*
* This file is part of Bisq.
*
* bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor.metric;
import bisq.monitor.AvailableTor;
import bisq.monitor.Metric;
import bisq.monitor.Monitor;
import bisq.monitor.OnionParser;
import bisq.monitor.Reporter;
import bisq.monitor.ThreadGate;
import bisq.core.account.witness.AccountAgeWitnessStore;
import bisq.core.proto.network.CoreNetworkProtoResolver;
import bisq.core.proto.persistable.CorePersistenceProtoResolver;
import bisq.core.trade.statistics.TradeStatistics3Store;
import bisq.network.p2p.CloseConnectionMessage;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.network.Connection;
import bisq.network.p2p.network.MessageListener;
import bisq.network.p2p.network.NetworkNode;
import bisq.network.p2p.network.TorNetworkNode;
import bisq.common.app.Version;
import bisq.common.config.BaseCurrencyNetwork;
import bisq.common.persistence.PersistenceManager;
import bisq.common.proto.network.NetworkEnvelope;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import java.time.Clock;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
/**
* Contacts a list of hosts and asks them for all the data excluding persisted messages. The
* answers are then compiled into buckets of message types. Based on these
* buckets, the Metric reports (for each host) the message types observed and
* their number.
*
* @author Florian Reimair
*
*/
@Slf4j
public abstract class P2PSeedNodeSnapshotBase extends Metric implements MessageListener {
private static final String HOSTS = "run.hosts";
private static final String TOR_PROXY_PORT = "run.torProxyPort";
private static final String DATABASE_DIR = "run.dbDir";
final Map<NodeAddress, Statistics<?>> bucketsPerHost = new ConcurrentHashMap<>();
private final ThreadGate gate = new ThreadGate();
protected final Set<byte[]> hashes = new TreeSet<>(Arrays::compare);
/**
* Statistics Interface for use with derived classes.
*
* @param <T> the value type of the statistics implementation
*/
protected abstract static class Statistics<T> {
protected final Map<String, T> buckets = new HashMap<>();
abstract void log(Object message);
Map<String, T> values() {
return buckets;
}
void reset() {
buckets.clear();
}
}
public P2PSeedNodeSnapshotBase(Reporter reporter) {
super(reporter);
}
@Override
public void configure(Properties properties) {
super.configure(properties);
if (hashes.isEmpty() && configuration.getProperty(DATABASE_DIR) != null) {
File dir = new File(configuration.getProperty(DATABASE_DIR));
String networkPostfix = "_" + BaseCurrencyNetwork.values()[Version.getBaseCurrencyNetwork()].toString();
try {
CorePersistenceProtoResolver persistenceProtoResolver = new CorePersistenceProtoResolver(null, null);
//TODO will not work with historical data... should be refactored to re-use code for reading resource files
TradeStatistics3Store tradeStatistics3Store = new TradeStatistics3Store();
PersistenceManager<TradeStatistics3Store> tradeStatistics3PersistenceManager = new PersistenceManager<>(dir,
persistenceProtoResolver, null);
tradeStatistics3PersistenceManager.initialize(tradeStatistics3Store,
tradeStatistics3Store.getDefaultStorageFileName() + networkPostfix,
PersistenceManager.Source.NETWORK);
TradeStatistics3Store persistedTradeStatistics3Store = tradeStatistics3PersistenceManager.getPersisted();
if (persistedTradeStatistics3Store != null) {
tradeStatistics3Store.getMap().putAll(persistedTradeStatistics3Store.getMap());
}
hashes.addAll(tradeStatistics3Store.getMap().keySet().stream()
.map(byteArray -> byteArray.bytes).collect(Collectors.toSet()));
AccountAgeWitnessStore accountAgeWitnessStore = new AccountAgeWitnessStore();
PersistenceManager<AccountAgeWitnessStore> accountAgeWitnessPersistenceManager = new PersistenceManager<>(dir,
persistenceProtoResolver, null);
accountAgeWitnessPersistenceManager.initialize(accountAgeWitnessStore,
accountAgeWitnessStore.getDefaultStorageFileName() + networkPostfix,
PersistenceManager.Source.NETWORK);
AccountAgeWitnessStore persistedAccountAgeWitnessStore = accountAgeWitnessPersistenceManager.getPersisted();
if (persistedAccountAgeWitnessStore != null) {
accountAgeWitnessStore.getMap().putAll(persistedAccountAgeWitnessStore.getMap());
}
hashes.addAll(accountAgeWitnessStore.getMap().keySet().stream()
.map(byteArray -> byteArray.bytes).collect(Collectors.toSet()));
} catch (NullPointerException e) {
// in case there is no store file
log.error("There is no storage file where there should be one: {}", dir.getAbsolutePath());
}
}
}
@Override
protected void execute() {
// start the network node
final NetworkNode networkNode = new TorNetworkNode(Integer.parseInt(configuration.getProperty(TOR_PROXY_PORT, "9054")),
new CoreNetworkProtoResolver(Clock.systemDefaultZone()), false,
new AvailableTor(Monitor.TOR_WORKING_DIR, "unused"), null);
// we do not need to start the networkNode, as we do not need the HS
//networkNode.start(this);
// clear our buckets
bucketsPerHost.clear();
getRequests().forEach(getDataRequest -> send(networkNode, getDataRequest));
report();
}
protected abstract List<NetworkEnvelope> getRequests();
protected void send(NetworkNode networkNode, NetworkEnvelope message) {
ArrayList<Thread> threadList = new ArrayList<>();
// for each configured host
for (String current : configuration.getProperty(HOSTS, "").split(",")) {
threadList.add(new Thread(() -> {
try {
// parse Url
NodeAddress target = OnionParser.getNodeAddress(current);
// do the data request
aboutToSend(message);
SettableFuture<Connection> future = networkNode.sendMessage(target, message);
Futures.addCallback(future, new FutureCallback<>() {
@Override
public void onSuccess(Connection connection) {
connection.addMessageListener(P2PSeedNodeSnapshotBase.this);
}
@Override
public void onFailure(@NotNull Throwable throwable) {
gate.proceed();
log.error(
"Sending {} failed. That is expected if the peer is offline.\n\tException={}", message.getClass().getSimpleName(), throwable.getMessage());
}
}, MoreExecutors.directExecutor());
} catch (Exception e) {
gate.proceed(); // release the gate on error
e.printStackTrace();
}
}, current));
}
gate.engage(threadList.size());
// start all threads and wait until they all finished. We do that so we can
// minimize the time between querying the hosts and therefore the chance of
// inconsistencies.
threadList.forEach(Thread::start);
gate.await();
}
protected void aboutToSend(NetworkEnvelope message) {
}
/**
* Report all the stuff. Uses the configured reporter directly.
*/
abstract void report();
@Override
public void onMessage(NetworkEnvelope networkEnvelope, Connection connection) {
if (treatMessage(networkEnvelope, connection)) {
gate.proceed();
} else if (networkEnvelope instanceof CloseConnectionMessage) {
gate.unlock();
} else {
log.warn("Got an unexpected message of type <{}>",
networkEnvelope.getClass().getSimpleName());
}
connection.removeMessageListener(this);
}
protected abstract boolean treatMessage(NetworkEnvelope networkEnvelope, Connection connection);
}

View file

@ -1,165 +0,0 @@
/*
* This file is part of Bisq.
*
* bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor.metric;
import bisq.monitor.Metric;
import bisq.monitor.OnionParser;
import bisq.monitor.Reporter;
import bisq.asset.Asset;
import bisq.asset.AssetRegistry;
import bisq.network.p2p.NodeAddress;
import org.berndpruenster.netlayer.tor.Tor;
import org.berndpruenster.netlayer.tor.TorCtlException;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import com.runjva.sourceforge.jsocks.protocol.SocksSocket;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Fetches fee and price data from the configured price nodes.
* Based on the work of HarryMcFinned.
*
* @author Florian Reimair
* @author HarryMcFinned
*
*/
@Slf4j
public class PriceNodeStats extends Metric {
private static final String HOSTS = "run.hosts";
private static final String IGNORE = "dashTxFee ltcTxFee dogeTxFee";
// poor mans JSON parser
private final Pattern stringNumberPattern = Pattern.compile("\"(.+)\" ?: ?(\\d+)");
private final Pattern pricePattern = Pattern.compile("\"price\" ?: ?([\\d.]+)");
private final Pattern currencyCodePattern = Pattern.compile("\"currencyCode\" ?: ?\"([A-Z]+)\"");
private final List<Object> assets = Arrays.asList(new AssetRegistry().stream().map(Asset::getTickerSymbol).toArray());
public PriceNodeStats(Reporter reporter) {
super(reporter);
}
@Override
protected void execute() {
try {
// fetch proxy
Tor tor = Tor.getDefault();
checkNotNull(tor, "tor must not be null");
Socks5Proxy proxy = tor.getProxy();
String[] hosts = configuration.getProperty(HOSTS, "").split(",");
Collections.shuffle(Arrays.asList(hosts));
// for each configured host
for (String current : hosts) {
Map<String, String> result = new HashMap<>();
// parse Url
NodeAddress tmp = OnionParser.getNodeAddress(current);
// connect
try {
SocksSocket socket = new SocksSocket(proxy, tmp.getHostName(), tmp.getPort());
// prepare to receive data
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// ask for fee data
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())));
out.println("GET /getFees/");
out.println();
out.flush();
// sift through the received lines and see if we got something json-like
String line;
while ((line = in.readLine()) != null) {
Matcher matcher = stringNumberPattern.matcher(line);
if (matcher.find())
if (!IGNORE.contains(matcher.group(1)))
result.put("fees." + matcher.group(1), matcher.group(2));
}
in.close();
out.close();
socket.close();
// connect
socket = new SocksSocket(proxy, tmp.getHostName(), tmp.getPort());
// prepare to receive data
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// ask for exchange rate data
out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())));
out.println("GET /getAllMarketPrices/");
out.println();
out.flush();
String currencyCode = "";
while ((line = in.readLine()) != null) {
Matcher currencyCodeMatcher = currencyCodePattern.matcher(line);
Matcher priceMatcher = pricePattern.matcher(line);
if (currencyCodeMatcher.find()) {
currencyCode = currencyCodeMatcher.group(1);
if (!assets.contains(currencyCode))
currencyCode = "";
} else if (!"".equals(currencyCode) && priceMatcher.find())
result.put("price." + currencyCode, priceMatcher.group(1));
}
// close all the things
in.close();
out.close();
socket.close();
// report
reporter.report(result, getName());
// only ask for data as long as we got none
if (!result.isEmpty())
break;
} catch (IOException e) {
log.error("{} seems to be down. Trying next configured price node.", tmp.getHostName());
e.printStackTrace();
}
}
} catch (TorCtlException | IOException e) {
e.printStackTrace();
}
}
}

View file

@ -1,82 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor.metric;
import bisq.monitor.Metric;
import bisq.monitor.Monitor;
import bisq.monitor.Reporter;
import bisq.monitor.ThreadGate;
import org.berndpruenster.netlayer.tor.HiddenServiceSocket;
import java.io.File;
import lombok.extern.slf4j.Slf4j;
/**
* A Metric to measure the startup time of a Tor Hidden Service on a already
* running Tor.
*
* @author Florian Reimair
*/
@Slf4j
public class TorHiddenServiceStartupTime extends Metric {
private static final String SERVICE_PORT = "run.servicePort";
private static final String LOCAL_PORT = "run.localPort";
private final String hiddenServiceDirectory = "metric_" + getName();
private final ThreadGate gate = new ThreadGate();
public TorHiddenServiceStartupTime(Reporter reporter) {
super(reporter);
}
@Override
protected void execute() {
// prepare settings. Fetch them every time we run the Metric so we do not have to
// restart on a config update
int localPort = Integer.parseInt(configuration.getProperty(LOCAL_PORT, "9998"));
int servicePort = Integer.parseInt(configuration.getProperty(SERVICE_PORT, "9999"));
// clear directory so we get a new onion address every time
new File(Monitor.TOR_WORKING_DIR + "/" + hiddenServiceDirectory).delete();
log.debug("creating the hidden service");
gate.engage();
// start timer - we do not need System.nanoTime as we expect our result to be in
// the range of tenth of seconds.
long start = System.currentTimeMillis();
HiddenServiceSocket hiddenServiceSocket = new HiddenServiceSocket(localPort, hiddenServiceDirectory,
servicePort);
hiddenServiceSocket.addReadyListener(socket -> {
// stop the timer and report
reporter.report(System.currentTimeMillis() - start, getName());
log.debug("the hidden service is ready");
gate.proceed();
return null;
});
gate.await();
log.debug("going to revoke the hidden service...");
hiddenServiceSocket.close();
log.debug("[going to revoke the hidden service...] done");
}
}

View file

@ -1,92 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor.metric;
import bisq.monitor.Metric;
import bisq.monitor.OnionParser;
import bisq.monitor.Reporter;
import bisq.monitor.StatisticsHelper;
import bisq.network.p2p.NodeAddress;
import org.berndpruenster.netlayer.tor.Tor;
import org.berndpruenster.netlayer.tor.TorCtlException;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import com.runjva.sourceforge.jsocks.protocol.SocksSocket;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* A Metric to measure the round-trip time to the Bisq seed nodes via plain tor.
*
* @author Florian Reimair
*/
public class TorRoundTripTime extends Metric {
private static final String SAMPLE_SIZE = "run.sampleSize";
private static final String HOSTS = "run.hosts";
public TorRoundTripTime(Reporter reporter) {
super(reporter);
}
@Override
protected void execute() {
SocksSocket socket;
try {
// fetch proxy
Tor tor = Tor.getDefault();
checkNotNull(tor, "tor must not be null");
Socks5Proxy proxy = tor.getProxy();
// for each configured host
for (String current : configuration.getProperty(HOSTS, "").split(",")) {
// parse Url
NodeAddress tmp = OnionParser.getNodeAddress(current);
List<Long> samples = new ArrayList<>();
while (samples.size() < Integer.parseInt(configuration.getProperty(SAMPLE_SIZE, "1"))) {
// start timer - we do not need System.nanoTime as we expect our result to be in
// seconds time.
long start = System.currentTimeMillis();
// connect
socket = new SocksSocket(proxy, tmp.getHostName(), tmp.getPort());
// by the time we get here, we are connected
samples.add(System.currentTimeMillis() - start);
// cleanup
socket.close();
}
// report
reporter.report(StatisticsHelper.process(samples), getName());
}
} catch (TorCtlException | IOException e) {
e.printStackTrace();
}
}
}

View file

@ -1,88 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor.metric;
import bisq.monitor.Metric;
import bisq.monitor.Reporter;
import org.berndpruenster.netlayer.tor.NativeTor;
import org.berndpruenster.netlayer.tor.Tor;
import org.berndpruenster.netlayer.tor.TorCtlException;
import org.berndpruenster.netlayer.tor.Torrc;
import java.io.File;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Properties;
/**
* A Metric to measure the deployment and startup time of the packaged Tor
* binaries.
*
* @author Florian Reimair
*/
public class TorStartupTime extends Metric {
private static final String SOCKS_PORT = "run.socksPort";
private final File torWorkingDirectory = new File("monitor/work/metric_torStartupTime");
private Torrc torOverrides;
public TorStartupTime(Reporter reporter) {
super(reporter);
}
@Override
public void configure(Properties properties) {
super.configure(properties);
synchronized (this) {
LinkedHashMap<String, String> overrides = new LinkedHashMap<>();
overrides.put("SOCKSPort", configuration.getProperty(SOCKS_PORT, "90500"));
try {
torOverrides = new Torrc(overrides);
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
protected void execute() {
// cleanup installation
torWorkingDirectory.delete();
Tor tor = null;
// start timer - we do not need System.nanoTime as we expect our result to be in
// tenth of seconds time.
long start = System.currentTimeMillis();
try {
tor = new NativeTor(torWorkingDirectory, null, torOverrides);
// stop the timer and set its timestamp
reporter.report(System.currentTimeMillis() - start, getName());
} catch (TorCtlException e) {
e.printStackTrace();
} finally {
// cleanup
if (tor != null)
tor.shutdown();
}
}
}

View file

@ -1,70 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor.reporter;
import bisq.monitor.Reporter;
import bisq.common.app.Version;
import bisq.common.config.BaseCurrencyNetwork;
import java.util.HashMap;
import java.util.Map;
/**
* A simple console reporter.
*
* @author Florian Reimair
*/
public class ConsoleReporter extends Reporter {
@Override
public void report(long value, String prefix) {
HashMap<String, String> result = new HashMap<>();
result.put("", String.valueOf(value));
report(result, prefix);
}
@Override
public void report(long value) {
HashMap<String, String> result = new HashMap<>();
result.put("", String.valueOf(value));
report(result);
}
@Override
public void report(Map<String, String> values, String prefix) {
String timestamp = String.valueOf(System.currentTimeMillis());
values.forEach((key, value) -> {
report(key, value, timestamp, prefix);
});
}
@Override
public void report(String key, String value, String timestamp, String prefix) {
System.err.println("Report: bisq" + (Version.getBaseCurrencyNetwork() != 0 ? "-" + BaseCurrencyNetwork.values()[Version.getBaseCurrencyNetwork()].getNetwork() : "")
+ (prefix.isEmpty() ? "" : "." + prefix)
+ (key.isEmpty() ? "" : "." + key)
+ " " + value + " " + timestamp);
}
@Override
public void report(Map<String, String> values) {
report(values, "");
}
}

View file

@ -1,106 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor.reporter;
import bisq.monitor.OnionParser;
import bisq.monitor.Reporter;
import bisq.network.p2p.NodeAddress;
import bisq.common.app.Version;
import bisq.common.config.BaseCurrencyNetwork;
import org.berndpruenster.netlayer.tor.TorSocket;
import com.google.common.base.Charsets;
import java.net.Socket;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* Reports our findings to a graphite service.
*
* @author Florian Reimair
*/
public class GraphiteReporter extends Reporter {
@Override
public void report(long value, String prefix) {
HashMap<String, String> result = new HashMap<>();
result.put("", String.valueOf(value));
report(result, prefix);
}
@Override
public void report(long value) {
report(value, "");
}
@Override
public void report(Map<String, String> values, String prefix) {
String timestamp = String.valueOf(System.currentTimeMillis());
values.forEach((key, value) -> {
report(key, value, timestamp, prefix);
try {
// give Tor some slack
// TODO maybe use the pickle protocol?
// https://graphite.readthedocs.io/en/latest/feeding-carbon.html
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
}
@Override
public void report(String key, String value, String timeInMilliseconds, String prefix) {
// https://graphite.readthedocs.io/en/latest/feeding-carbon.html
String report = "bisq" + (Version.getBaseCurrencyNetwork() != 0 ? "-" + BaseCurrencyNetwork.values()[Version.getBaseCurrencyNetwork()].getNetwork() : "")
+ (prefix.isEmpty() ? "" : "." + prefix)
+ (key.isEmpty() ? "" : "." + key)
+ " " + value + " " + Long.parseLong(timeInMilliseconds) / 1000 + "\n";
try {
NodeAddress nodeAddress = OnionParser.getNodeAddress(configuration.getProperty("serviceUrl"));
Socket socket;
if (nodeAddress.getFullAddress().contains(".onion"))
socket = new TorSocket(nodeAddress.getHostName(), nodeAddress.getPort());
else
socket = new Socket(nodeAddress.getHostName(), nodeAddress.getPort());
socket.getOutputStream().write(report.getBytes(Charsets.UTF_8));
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void report(Map<String, String> values) {
report(values, "");
}
}

View file

@ -1,12 +0,0 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
<logger name="org.berndpruenster.netlayer.tor" level="INFO"/>
</configuration>

View file

@ -1,79 +0,0 @@
## System configuration
# true overwrites the reporters picked by the developers (for debugging for example) (defaults to false)
System.useConsoleReporter=true
# 0 -> BTC_MAINNET, 1 -> BTC_TESTNET (default)
System.baseCurrencyNetwork=0
## Each Metric is configured via a set of properties.
##
## The minimal set of properties required to run a Metric is:
##
## YourMetricName.enabled=true|false
## YourMetricName.run.interval=10 [seconds]
#Edit and uncomment the lines below to your liking
#TorStartupTime Metric
TorStartupTime.enabled=false
TorStartupTime.run.interval=100
TorStartupTime.run.socksPort=90500
TorRoundTripTime.enabled=false
TorRoundTripTime.run.interval=100
TorRoundTripTime.run.sampleSize=3
# torproject.org hidden service
TorRoundTripTime.run.hosts=http://expyuzz4wqqyqhjn.onion:80
#TorHiddenServiceStartupTime Metric
TorHiddenServiceStartupTime.enabled=false
TorHiddenServiceStartupTime.run.interval=100
TorHiddenServiceStartupTime.run.localPort=90501
TorHiddenServiceStartupTime.run.servicePort=90511
#P2PRoundTripTime Metric
P2PRoundTripTime.enabled=false
P2PRoundTripTime.run.interval=100
P2PRoundTripTime.run.sampleSize=5
P2PRoundTripTime.run.hosts=723ljisnynbtdohi.onion:8000, fl3mmribyxgrv63c.onion:8000
P2PRoundTripTime.run.torProxyPort=9060
#P2PNetworkLoad Metric
P2PNetworkLoad.enabled=false
P2PNetworkLoad.run.interval=100
P2PNetworkLoad.run.torProxyPort=9061
P2PNetworkLoad.run.historySize=200
#P2PSeedNodeSnapshotBase Metric
P2PSeedNodeSnapshot.enabled=true
P2PSeedNodeSnapshot.run.dbDir=bisq/p2p/build/resources/main/
P2PSeedNodeSnapshot.run.interval=24
P2PSeedNodeSnapshot.run.hosts=3f3cu2yw7u457ztq.onion:8000, 723ljisnynbtdohi.onion:8000, fl3mmribyxgrv63c.onion:8000
P2PSeedNodeSnapshot.run.torProxyPort=9062
#P2PMarketStats Metric
P2PMarketStats.enabled=false
P2PMarketStats.run.interval=37
P2PMarketStats.run.dbDir=bisq/p2p/build/resources/main/
P2PMarketStats.run.hosts=ef5qnzx6znifo3df.onion:8000
P2PMarketStats.run.torProxyPort=9063
#PriceNodeStats Metric
PriceNodeStats.enabled=false
PriceNodeStats.run.interval=42
PriceNodeStats.run.hosts=http://xc3nh4juf2hshy7e.onion, http://44mgyoe2b6oqiytt.onion, http://62nvujg5iou3vu3i.onion, http://ceaanhbvluug4we6.onion, http://gztmprecgqjq64zh.onion/
#MarketStats Metric
MarketStats.enabled=false
MarketStats.run.interval=191
#Another Metric
Another.run.interval=5
## Reporters are configured via a set of properties as well.
##
## In contrast to Metrics, Reporters do not have a minimal set of properties.
#GraphiteReporter
GraphiteReporter.serviceUrl=k6evlhg44acpchtc.onion:2003

View file

@ -1,149 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor;
import bisq.monitor.reporter.ConsoleReporter;
import java.util.HashMap;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import org.junit.Assert;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
@Disabled
public class MonitorInfrastructureTests {
/**
* A dummy metric for development purposes.
*/
public class Dummy extends Metric {
public Dummy() {
super(new ConsoleReporter());
}
public boolean active() {
return enabled();
}
@Override
protected void execute() {
try {
Thread.sleep(50000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
@ParameterizedTest
@ValueSource(strings = {"empty", "no interval", "typo"})
public void basicConfigurationError(String configuration) {
HashMap<String, Properties> lut = new HashMap<>();
lut.put("empty", new Properties());
Properties noInterval = new Properties();
noInterval.put("Dummy.enabled", "true");
lut.put("no interval", noInterval);
Properties typo = new Properties();
typo.put("Dummy.enabled", "true");
//noinspection SpellCheckingInspection
typo.put("Dummy.run.inteval", "1");
lut.put("typo", typo);
Dummy DUT = new Dummy();
DUT.configure(lut.get(configuration));
Assert.assertFalse(DUT.active());
}
@Test
public void basicConfigurationSuccess() throws Exception {
Properties correct = new Properties();
correct.put("Dummy.enabled", "true");
correct.put("Dummy.run.interval", "1");
Dummy DUT = new Dummy();
DUT.configure(correct);
Assert.assertTrue(DUT.active());
// graceful shutdown
Metric.haltAllMetrics();
}
@Test
public void reloadConfig() throws InterruptedException, ExecutionException {
// our dummy
Dummy DUT = new Dummy();
// a second dummy to run as well
Dummy DUT2 = new Dummy();
DUT2.setName("Dummy2");
Properties dummy2Properties = new Properties();
dummy2Properties.put("Dummy2.enabled", "true");
dummy2Properties.put("Dummy2.run.interval", "1");
DUT2.configure(dummy2Properties);
// disable
DUT.configure(new Properties());
Assert.assertFalse(DUT.active());
Assert.assertTrue(DUT2.active());
// enable
Properties properties = new Properties();
properties.put("Dummy.enabled", "true");
properties.put("Dummy.run.interval", "1");
DUT.configure(properties);
Assert.assertTrue(DUT.active());
Assert.assertTrue(DUT2.active());
// disable again
DUT.configure(new Properties());
Assert.assertFalse(DUT.active());
Assert.assertTrue(DUT2.active());
// enable again
DUT.configure(properties);
Assert.assertTrue(DUT.active());
Assert.assertTrue(DUT2.active());
// graceful shutdown
Metric.haltAllMetrics();
}
@Test
public void shutdown() {
Dummy DUT = new Dummy();
DUT.setName("Dummy");
Properties dummyProperties = new Properties();
dummyProperties.put("Dummy.enabled", "true");
dummyProperties.put("Dummy.run.interval", "1");
DUT.configure(dummyProperties);
try {
Thread.sleep(2000);
Metric.haltAllMetrics();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

View file

@ -1,118 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor;
import bisq.monitor.metric.P2PNetworkLoad;
import bisq.monitor.reporter.ConsoleReporter;
import org.berndpruenster.netlayer.tor.NativeTor;
import org.berndpruenster.netlayer.tor.Tor;
import org.berndpruenster.netlayer.tor.TorCtlException;
import java.util.Map;
import java.util.Properties;
import org.junit.Assert;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Test the round trip time metric against the hidden service of tor project.org.
*
* @author Florian Reimair
*/
@Disabled
class P2PNetworkLoadTests {
/**
* A dummy Reporter for development purposes.
*/
private class DummyReporter extends ConsoleReporter {
private Map<String, String> results;
@Override
public void report(long value) {
Assert.fail();
}
Map<String, String> hasResults() {
return results;
}
@Override
public void report(Map<String, String> values) {
Assert.fail();
}
@Override
public void report(long value, String prefix) {
Assert.fail();
}
@Override
public void report(Map<String, String> values, String prefix) {
super.report(values, prefix);
results = values;
}
}
@BeforeAll
static void setup() throws TorCtlException {
// simulate the tor instance available to all metrics
Tor.setDefault(new NativeTor(Monitor.TOR_WORKING_DIR));
}
@Test
void run() throws Exception {
DummyReporter reporter = new DummyReporter();
// configure
Properties configuration = new Properties();
configuration.put("P2PNetworkLoad.enabled", "true");
configuration.put("P2PNetworkLoad.run.interval", "10");
configuration.put("P2PNetworkLoad.run.hosts",
"http://fl3mmribyxgrv63c.onion:8000, http://3f3cu2yw7u457ztq.onion:8000");
Metric DUT = new P2PNetworkLoad(reporter);
// start
DUT.configure(configuration);
// give it some time to start and then stop
while (!DUT.enabled())
Thread.sleep(500);
Thread.sleep(20000);
Metric.haltAllMetrics();
// observe results
Map<String, String> results = reporter.hasResults();
Assert.assertFalse(results.isEmpty());
}
@AfterAll
static void cleanup() {
Tor tor = Tor.getDefault();
checkNotNull(tor, "tor must not be null");
tor.shutdown();
}
}

View file

@ -1,136 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor;
import bisq.monitor.metric.P2PRoundTripTime;
import bisq.monitor.reporter.ConsoleReporter;
import org.berndpruenster.netlayer.tor.NativeTor;
import org.berndpruenster.netlayer.tor.Tor;
import org.berndpruenster.netlayer.tor.TorCtlException;
import java.util.Map;
import java.util.Properties;
import org.junit.Assert;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Test the round trip time metric against the hidden service of tor project.org.
*
* @author Florian Reimair
*/
@Disabled
class P2PRoundTripTimeTests {
/**
* A dummy Reporter for development purposes.
*/
private class DummyReporter extends ConsoleReporter {
private Map<String, String> results;
@Override
public void report(long value) {
Assert.fail();
}
Map<String, String> hasResults() {
return results;
}
@Override
public void report(Map<String, String> values) {
Assert.fail();
}
@Override
public void report(long value, String prefix) {
Assert.fail();
}
@Override
public void report(Map<String, String> values, String prefix) {
super.report(values, prefix);
results = values;
}
}
@BeforeAll
static void setup() throws TorCtlException {
// simulate the tor instance available to all metrics
Tor.setDefault(new NativeTor(Monitor.TOR_WORKING_DIR));
}
@ParameterizedTest
@ValueSource(strings = {"default", "3", "4", "10"})
void run(String sampleSize) throws Exception {
DummyReporter reporter = new DummyReporter();
// configure
Properties configuration = new Properties();
configuration.put("P2PRoundTripTime.enabled", "true");
configuration.put("P2PRoundTripTime.run.interval", "2");
if (!"default".equals(sampleSize))
configuration.put("P2PRoundTripTime.run.sampleSize", sampleSize);
// torproject.org hidden service
configuration.put("P2PRoundTripTime.run.hosts", "http://fl3mmribyxgrv63c.onion:8000");
configuration.put("P2PRoundTripTime.run.torProxyPort", "9052");
Metric DUT = new P2PRoundTripTime(reporter);
// start
DUT.configure(configuration);
// give it some time to start and then stop
while (!DUT.enabled())
Thread.sleep(2000);
Metric.haltAllMetrics();
// observe results
Map<String, String> results = reporter.hasResults();
Assert.assertFalse(results.isEmpty());
Assert.assertEquals(results.get("sampleSize"), sampleSize.equals("default") ? "1" : sampleSize);
Integer p25 = Integer.valueOf(results.get("p25"));
Integer p50 = Integer.valueOf(results.get("p50"));
Integer p75 = Integer.valueOf(results.get("p75"));
Integer min = Integer.valueOf(results.get("min"));
Integer max = Integer.valueOf(results.get("max"));
Integer average = Integer.valueOf(results.get("average"));
Assert.assertTrue(0 < min);
Assert.assertTrue(min <= p25 && p25 <= p50);
Assert.assertTrue(p50 <= p75);
Assert.assertTrue(p75 <= max);
Assert.assertTrue(min <= average && average <= max);
}
@AfterAll
static void cleanup() {
Tor tor = Tor.getDefault();
checkNotNull(tor, "tor must not be null");
tor.shutdown();
}
}

View file

@ -1,115 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor;
import bisq.monitor.metric.PriceNodeStats;
import org.berndpruenster.netlayer.tor.NativeTor;
import org.berndpruenster.netlayer.tor.Tor;
import org.berndpruenster.netlayer.tor.TorCtlException;
import java.io.File;
import java.util.Map;
import java.util.Properties;
import org.junit.Assert;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* @author Florian Reimair
*/
@Disabled
public class PriceNodeStatsTests {
private final static File torWorkingDirectory = new File("monitor/" + PriceNodeStatsTests.class.getSimpleName());
/**
* A dummy Reporter for development purposes.
*/
private class DummyReporter extends Reporter {
private Map<String, String> results;
@Override
public void report(long value) {
Assert.fail();
}
public Map<String, String> results() {
return results;
}
@Override
public void report(Map<String, String> values) {
results = values;
}
@Override
public void report(Map<String, String> values, String prefix) {
report(values);
}
@Override
public void report(String key, String value, String timestamp, String prefix) {
}
@Override
public void report(long value, String prefix) {
report(value);
}
}
@BeforeAll
public static void setup() throws TorCtlException {
// simulate the tor instance available to all metrics
Tor.setDefault(new NativeTor(torWorkingDirectory));
}
@Test
public void connect() {
DummyReporter reporter = new DummyReporter();
Metric DUT = new PriceNodeStats(reporter);
Properties configuration = new Properties();
configuration.put("PriceNodeStats.run.hosts", "http://5bmpx76qllutpcyp.onion");
DUT.configure(configuration);
DUT.execute();
Assert.assertNotNull(reporter.results());
Assert.assertTrue(reporter.results.size() > 0);
}
@AfterAll
public static void cleanup() {
Tor tor = Tor.getDefault();
checkNotNull(tor, "tor must not be null");
tor.shutdown();
torWorkingDirectory.delete();
}
}

View file

@ -1,115 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor;
import bisq.monitor.metric.TorHiddenServiceStartupTime;
import org.berndpruenster.netlayer.tor.NativeTor;
import org.berndpruenster.netlayer.tor.Tor;
import org.berndpruenster.netlayer.tor.TorCtlException;
import java.io.File;
import java.util.Map;
import java.util.Properties;
import org.junit.Assert;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static com.google.common.base.Preconditions.checkNotNull;
@Disabled // Ignore for normal test runs as the tests take lots of time
public class TorHiddenServiceStartupTimeTests {
private final static File torWorkingDirectory = new File("monitor/" + TorHiddenServiceStartupTimeTests.class.getSimpleName());
/**
* A dummy Reporter for development purposes.
*/
private class DummyReporter extends Reporter {
private long result;
@Override
public void report(long value) {
result = value;
}
public long results() {
return result;
}
@Override
public void report(Map<String, String> values) {
report(Long.parseLong(values.values().iterator().next()));
}
@Override
public void report(Map<String, String> values, String prefix) {
report(values);
}
@Override
public void report(String key, String value, String timestamp, String prefix) {
}
@Override
public void report(long value, String prefix) {
report(value);
}
}
@BeforeAll
public static void setup() throws TorCtlException {
// simulate the tor instance available to all metrics
Tor.setDefault(new NativeTor(torWorkingDirectory));
}
@Test
public void run() throws Exception {
DummyReporter reporter = new DummyReporter();
// configure
Properties configuration = new Properties();
configuration.put("TorHiddenServiceStartupTime.enabled", "true");
configuration.put("TorHiddenServiceStartupTime.run.interval", "5");
Metric DUT = new TorHiddenServiceStartupTime(reporter);
// start
DUT.configure(configuration);
// give it some time and then stop
Thread.sleep(180 * 1000);
Metric.haltAllMetrics();
// observe results
Assert.assertTrue(reporter.results() > 0);
}
@AfterAll
public static void cleanup() {
Tor tor = Tor.getDefault();
checkNotNull(tor, "tor must not be null");
tor.shutdown();
torWorkingDirectory.delete();
}
}

View file

@ -1,142 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor;
import bisq.monitor.metric.TorRoundTripTime;
import org.berndpruenster.netlayer.tor.NativeTor;
import org.berndpruenster.netlayer.tor.Tor;
import org.berndpruenster.netlayer.tor.TorCtlException;
import java.io.File;
import java.util.Map;
import java.util.Properties;
import org.junit.Assert;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Test the round trip time metric against the hidden service of tor project.org.
*
* @author Florian Reimair
*/
@Disabled // Ignore for normal test runs as the tests take lots of time
public class TorRoundTripTimeTests {
/**
* A dummy Reporter for development purposes.
*/
private class DummyReporter extends Reporter {
private Map<String, String> results;
@Override
public void report(long value) {
Assert.fail();
}
public Map<String, String> hasResults() {
return results;
}
@Override
public void report(Map<String, String> values) {
results = values;
}
@Override
public void report(Map<String, String> values, String prefix) {
report(values);
}
@Override
public void report(String key, String value, String timestamp, String prefix) {
}
@Override
public void report(long value, String prefix) {
report(value);
}
}
private static final File workingDirectory = new File(TorRoundTripTimeTests.class.getSimpleName());
@BeforeAll
public static void setup() throws TorCtlException {
// simulate the tor instance available to all metrics
Tor.setDefault(new NativeTor(workingDirectory));
}
@ParameterizedTest
@ValueSource(strings = {"default", "3", "4", "10"})
public void run(String sampleSize) throws Exception {
DummyReporter reporter = new DummyReporter();
// configure
Properties configuration = new Properties();
configuration.put("TorRoundTripTime.enabled", "true");
configuration.put("TorRoundTripTime.run.interval", "2");
if (!"default".equals(sampleSize))
configuration.put("TorRoundTripTime.run.sampleSize", sampleSize);
// torproject.org hidden service
configuration.put("TorRoundTripTime.run.hosts", "http://expyuzz4wqqyqhjn.onion:80");
Metric DUT = new TorRoundTripTime(reporter);
// start
DUT.configure(configuration);
// give it some time to start and then stop
Thread.sleep(100);
Metric.haltAllMetrics();
// observe results
Map<String, String> results = reporter.hasResults();
Assert.assertFalse(results.isEmpty());
Assert.assertEquals(results.get("sampleSize"), sampleSize.equals("default") ? "1" : sampleSize);
Integer p25 = Integer.valueOf(results.get("p25"));
Integer p50 = Integer.valueOf(results.get("p50"));
Integer p75 = Integer.valueOf(results.get("p75"));
Integer min = Integer.valueOf(results.get("min"));
Integer max = Integer.valueOf(results.get("max"));
Integer average = Integer.valueOf(results.get("average"));
Assert.assertTrue(0 < min);
Assert.assertTrue(min <= p25 && p25 <= p50);
Assert.assertTrue(p50 <= p75);
Assert.assertTrue(p75 <= max);
Assert.assertTrue(min <= average && average <= max);
}
@AfterAll
public static void cleanup() {
Tor tor = Tor.getDefault();
checkNotNull(tor, "tor must not be null");
tor.shutdown();
workingDirectory.delete();
}
}

View file

@ -1,92 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.monitor;
import bisq.monitor.metric.TorStartupTime;
import java.util.Map;
import java.util.Properties;
import org.junit.Assert;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@Disabled // Ignore for normal test runs as the tests take lots of time
public class TorStartupTimeTests {
/**
* A dummy Reporter for development purposes.
*/
private class DummyReporter extends Reporter {
private long result;
@Override
public void report(long value) {
result = value;
}
public long results() {
return result;
}
@Override
public void report(Map<String, String> values) {
report(Long.parseLong(values.values().iterator().next()));
}
@Override
public void report(Map<String, String> values, String prefix) {
report(values);
}
@Override
public void report(String key, String value, String timestamp, String prefix) {
}
@Override
public void report(long value, String prefix) {
report(value);
}
}
@Test
public void run() throws Exception {
DummyReporter reporter = new DummyReporter();
// configure
Properties configuration = new Properties();
configuration.put("TorStartupTime.enabled", "true");
configuration.put("TorStartupTime.run.interval", "2");
configuration.put("TorStartupTime.run.socksPort", "9999");
Metric DUT = new TorStartupTime(reporter);
// start
DUT.configure(configuration);
// give it some time and then stop
Thread.sleep(15 * 1000);
Metric.haltAllMetrics();
// TODO Test fails due timing issue
// observe results
Assert.assertTrue(reporter.results() > 0);
}
}

View file

@ -1,11 +0,0 @@
#!/bin/sh
echo "[*] Stopping Bisq Server monitoring utensils"
echo ' '
echo 'This script will not remove any configuration or binaries from the system. It just stops the services.'
sleep 10
sudo systemctl stop nginx
sudo systemctl stop collectd
sudo systemctl disable nginx
sudo systemctl disable collectd
echo "[*] Done!"

View file

@ -1 +0,0 @@
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

@ -1,42 +0,0 @@
Deploy on Heroku
--------
Run the following commands:
heroku create
heroku buildpacks:add heroku/gradle
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

View file

@ -1,86 +0,0 @@
# bisq-pricenode
## Overview
The Bisq pricenode is a simple HTTP service that fetches, transforms and relays data from third-party price providers to Bisq exchange clients on request. Available prices include:
- Bitcoin exchange rates, available at `/getAllMarketPrices`, and
- Bitcoin mining fee rates, available at `/getFees`
Pricenodes are deployed in production as Tor hidden services. This is not because the location of these nodes needs to be kept secret, but rather so that Bisq exchange clients do not need to exit the Tor network in order to get price data.
Anyone can run a pricenode, but it must be _discoverable_ in order for it to do any good. For exchange clients to discover your pricenode, its .onion address must be hard-coded in the Bisq exchange client's `ProvidersRepository` class. Alternatively, users can point explicitly to given pricenode (or set of pricenodes) with the exchange client's `--providers` command line option.
Pricenodes can be deployed anywhere Java and Tor binaries can be run. Instructions below cover deployment on localhost, and instructions [how to deploy on Heroku](README-HEROKU.md) are also available.
Pricenodes should be cheap to run with regard to both time and money. The application itself is non-resource intensive and can be run on the low-end of most providers' paid tiers.
A [pricenode operator](https://github.com/bisq-network/roles/issues/5)'s main responsibilities are to ensure their node(s) are available and up-to-date. Releases are currently source-only, with the assumption that most operators will favor Git-based "push to deploy" workflows. To stay up to date with releases, operators can [subscribe to this repository's releases.atom feed](https://github.com/bisq-network/pricenode/releases.atom) and/or get notifications in the `#pricenode` Slack channel.
Operating a production pricenode is a valuable service to the Bisq network, and operators should issue BSQ compensation requests accordingly.
## Prerequisites for running a pricenode
To run a pricenode, you will need:
- JDK 8 if you want to build and run a node locally.
- The `tor` binary (e.g. `brew install tor`) if you want to run a hidden service locally.
## How to deploy for production
### Install
Run the one-command installer:
```bash
curl -s https://raw.githubusercontent.com/bisq-network/bisq/master/pricenode/install_pricenode_debian.sh | sudo bash
```
At the end of the installer script, it should print your Tor onion hostname.
### Test
To manually test endpoints, run each of the following:
``` bash
curl http://localhost:8080/getAllMarketPrices
curl http://localhost:8080/getFees
curl http://localhost:8080/getParams
curl http://localhost:8080/info
```
### Monitoring
If you run a main pricenode, you also are obliged to activate the monitoring feed by running
```bash
bash <(curl -s https://raw.githubusercontent.com/bisq-network/bisq/master/monitor/install_collectd_debian.sh)
```
Follow the instruction given by the script and report your certificate to the [@bisq-network/monitoring](https://github.com/orgs/bisq-network/teams/monitoring-operators) team.
Furthermore, you are obliged to provide network size data to the monitor by running
```bash
curl -s https://raw.githubusercontent.com/bisq-network/bisq/master/pricenode/install_networksize_debian.sh | sudo bash
```
### Updating
Update your bisq code in /bisq/bisq with ```git pull```
Then build an updated pricenode:
```./gradlew :pricenode:installDist -x test```
## How to deploy elsewhere
- [README-HEROKU.md](README-HEROKU.md)
- [docker/README.md](docker/README.md)
## Bitcoin mining fee estimates
The pricenode exposes a service API to Bisq clients under `/getFees`.
This API returns a mining fee rate estimate, representing an average of several mining fee rate values retrieved from different `mempool.space` instances.
To configure which `mempool.space` instances are queried to calculate this average, see the relevant section in the file `application.properties`.

View file

@ -1,20 +0,0 @@
# 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

View file

@ -1 +0,0 @@
JAVA_OPTS="-XX:+ExitOnOutOfMemoryError"

View file

@ -1,22 +0,0 @@
[Unit]
Description=Bisq Price Node
After=network.target
[Service]
SyslogIdentifier=bisq-pricenode
EnvironmentFile=/etc/default/bisq-pricenode.env
ExecStart=/bisq/bisq/bisq-pricenode 2
ExecStop=/bin/kill -TERM ${MAINPID}
Restart=on-failure
User=bisq
Group=bisq
PrivateTmp=true
ProtectSystem=full
NoNewPrivileges=true
PrivateDevices=true
MemoryDenyWriteExecute=false
[Install]
WantedBy=multi-user.target

View file

@ -1,5 +0,0 @@
LoadPlugin exec
<Plugin exec>
Exec "__USER_GROUP__" "__SCRAPERSCRIPT__"
</Plugin>

View file

@ -1,26 +0,0 @@
###
# 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

@ -1,43 +0,0 @@
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

@ -1,103 +0,0 @@
#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

@ -1,21 +0,0 @@
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

@ -1,4 +0,0 @@
#!/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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,41 +0,0 @@
#!/bin/sh
set -e
echo "[*] Network Size Monitoring installation script"
##### change paths if necessary for your system
ROOT_USER=root
SCRAPER_HOME=/journalreader
SCRAPER_USER=journalreader
SCRAPER_GROUP=systemd-journal
#####
echo "[*] Checking environment..."
if [ ! -f "/etc/collectd/collectd.conf" ]; then
echo 'Collectd is not installed. Did you do the install_monitoring_debian.sh?'
echo 'Exiting...'
exit
fi
if ! grep -q "journalreader" /etc/passwd; then
echo 'User not found. Did you run the install_networksize_debian.sh?'
echo 'Exiting...'
exit
fi
echo "[*] Installing journal parser script"
curl -s https://raw.githubusercontent.com/bisq-network/bisq/master/pricenode/journalscraper_hsversion.sh > /tmp/journalscraper_hsversion.sh
sudo -H -i -u "${ROOT_USER}" install -c -o "${SCRAPER_USER}" -g "${SCRAPER_GROUP}" -m 744 /tmp/journalscraper_hsversion.sh "${SCRAPER_HOME}/scraperscript_hsversion.sh"
echo "[*] Installing collectd config"
curl -s https://raw.githubusercontent.com/bisq-network/bisq/master/pricenode/collectd.conf.snippet > /tmp/collectd.conf.snippet
sudo -H -i -u "${ROOT_USER}" sed -i -e "s/LoadPlugin exec//" /tmp/collectd.conf.snippet
sudo -H -i -u "${ROOT_USER}" /bin/sh -c "cat /tmp/collectd.conf.snippet >> /etc/collectd/collectd.conf"
sudo -H -i -u "${ROOT_USER}" sed -i -e "s/__USER_GROUP__/${SCRAPER_USER}:${SCRAPER_GROUP}/" /etc/collectd/collectd.conf
sudo -H -i -u "${ROOT_USER}" sed -i -e "s!__SCRAPERSCRIPT__!${SCRAPER_HOME}/scraperscript_hsversion.sh!" /etc/collectd/collectd.conf
echo "[*] Restarting services"
sudo -H -i -u "${ROOT_USER}" systemctl restart collectd.service
echo '[*] Done!'

View file

@ -1,42 +0,0 @@
#!/bin/sh
set -e
echo "[*] Network Size Monitoring installation script"
##### change paths if necessary for your system
ROOT_USER=root
SCRAPER_HOME=/journalreader
SCRAPER_USER=journalreader
SCRAPER_GROUP=systemd-journal
#####
echo "[*] Checking environment..."
if [ ! -f "/etc/collectd/collectd.conf" ]; then
echo 'Collectd is not installed. Did you do the install_monitoring_debian.sh?'
echo 'Exiting...'
exit
fi
echo "[*] Creating journal reader user"
sudo -H -i -u "${ROOT_USER}" useradd -d "${SCRAPER_HOME}" -G "${SCRAPER_GROUP}" "${SCRAPER_USER}"
sudo -H -i -u "${ROOT_USER}" mkdir -p "${SCRAPER_HOME}"
sudo -H -i -u "${ROOT_USER}" chown "${SCRAPER_USER}":"${SCRAPER_GROUP}" ${SCRAPER_HOME}
echo "[*] Installing journal parser script"
curl -s https://raw.githubusercontent.com/bisq-network/bisq/master/pricenode/journalscraper.sh > /tmp/journalscraper.sh
sudo -H -i -u "${ROOT_USER}" install -c -o "${SCRAPER_USER}" -g "${SCRAPER_GROUP}" -m 744 /tmp/journalscraper.sh "${SCRAPER_HOME}/scraperscript.sh"
echo "[*] Installing collectd config"
curl -s https://raw.githubusercontent.com/bisq-network/bisq/master/pricenode/collectd.conf.snippet > /tmp/collectd.conf.snippet
sudo -H -i -u "${ROOT_USER}" /bin/sh -c "cat /tmp/collectd.conf.snippet >> /etc/collectd/collectd.conf"
sudo -H -i -u "${ROOT_USER}" sed -i -e "s/__USER_GROUP__/${SCRAPER_USER}:${SCRAPER_GROUP}/" /etc/collectd/collectd.conf
sudo -H -i -u "${ROOT_USER}" sed -i -e "s!__SCRAPERSCRIPT__!${SCRAPER_HOME}/scraperscript.sh!" /etc/collectd/collectd.conf
sudo -H -i -u "${ROOT_USER}" systemctl enable collectd.service
echo "[*] Restarting services"
sudo -H -i -u "${ROOT_USER}" systemctl restart collectd.service
echo '[*] Done!'

View file

@ -1,90 +0,0 @@
#!/usr/bin/env bash
set -e
echo "[*] Bisq bisq-pricenode installation script"
##### change as necessary for your system
SYSTEMD_SERVICE_HOME=/etc/systemd/system
SYSTEMD_ENV_HOME=/etc/default
ROOT_USER=root
ROOT_GROUP=root
#ROOT_HOME=/root
BISQ_USER=bisq
BISQ_GROUP=bisq
BISQ_HOME=/bisq
BISQ_REPO_URL=https://github.com/bisq-network/bisq
BISQ_REPO_NAME=bisq
BISQ_REPO_TAG=master
BISQ_LATEST_RELEASE=master
BISQ_TORHS=pricenode
TOR_PKG="tor"
#TOR_USER=debian-tor
TOR_GROUP=debian-tor
TOR_CONF=/etc/tor/torrc
TOR_RESOURCES=/var/lib/tor
#####
echo "[*] Upgrading apt packages"
sudo -H -i -u "${ROOT_USER}" DEBIAN_FRONTEND=noninteractive apt-get update -q
sudo -H -i -u "${ROOT_USER}" DEBIAN_FRONTEND=noninteractive apt-get upgrade -qq -y
echo "[*] Installing Tor"
sudo -H -i -u "${ROOT_USER}" DEBIAN_FRONTEND=noninteractive apt-get install -qq -y "${TOR_PKG}"
echo "[*] Adding Tor configuration"
if ! grep "${BISQ_TORHS}" /etc/tor/torrc >/dev/null 2>&1;then
sudo -H -i -u "${ROOT_USER}" sh -c "echo HiddenServiceDir ${TOR_RESOURCES}/${BISQ_TORHS}/ >> ${TOR_CONF}"
sudo -H -i -u "${ROOT_USER}" sh -c "echo HiddenServicePort 80 127.0.0.1:8080 >> ${TOR_CONF}"
sudo -H -i -u "${ROOT_USER}" sh -c "echo HiddenServiceVersion 3 >> ${TOR_CONF}"
fi
echo "[*] Creating Bisq user with Tor access"
sudo -H -i -u "${ROOT_USER}" useradd -d "${BISQ_HOME}" -G "${TOR_GROUP}" "${BISQ_USER}"
echo "[*] Creating Bisq homedir"
sudo -H -i -u "${ROOT_USER}" mkdir -p "${BISQ_HOME}"
sudo -H -i -u "${ROOT_USER}" chown "${BISQ_USER}":"${BISQ_GROUP}" ${BISQ_HOME}
echo "[*] Cloning Bisq repo"
sudo -H -i -u "${BISQ_USER}" git config --global advice.detachedHead false
sudo -H -i -u "${BISQ_USER}" git clone --branch "${BISQ_REPO_TAG}" "${BISQ_REPO_URL}" "${BISQ_HOME}/${BISQ_REPO_NAME}"
echo "[*] Installing OpenJDK 11"
sudo -H -i -u "${ROOT_USER}" apt-get install -qq -y openjdk-11-jdk
echo "[*] Checking out Bisq ${BISQ_LATEST_RELEASE}"
sudo -H -i -u "${BISQ_USER}" sh -c "cd ${BISQ_HOME}/${BISQ_REPO_NAME} && git checkout ${BISQ_LATEST_RELEASE}"
echo "[*] Building Bisq from source"
sudo -H -i -u "${BISQ_USER}" sh -c "cd ${BISQ_HOME}/${BISQ_REPO_NAME} && ./gradlew :pricenode:installDist -x test < /dev/null" # redirect from /dev/null is necessary to workaround gradlew non-interactive shell hanging issue
echo "[*] Installing bisq-pricenode systemd service"
sudo -H -i -u "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${BISQ_HOME}/${BISQ_REPO_NAME}/pricenode/bisq-pricenode.service" "${SYSTEMD_SERVICE_HOME}"
sudo -H -i -u "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${BISQ_HOME}/${BISQ_REPO_NAME}/pricenode/bisq-pricenode.env" "${SYSTEMD_ENV_HOME}"
echo "[*] Reloading systemd daemon configuration"
sudo -H -i -u "${ROOT_USER}" systemctl daemon-reload
echo "[*] Enabling bisq-pricenode service"
sudo -H -i -u "${ROOT_USER}" systemctl enable bisq-pricenode.service
echo "[*] Starting bisq-pricenode service"
sudo -H -i -u "${ROOT_USER}" systemctl start bisq-pricenode.service
sleep 5
sudo -H -i -u "${ROOT_USER}" journalctl --no-pager --unit bisq-pricenode
echo "[*] Restarting Tor"
sudo -H -i -u "${ROOT_USER}" service tor restart
sleep 5
echo '[*] Done!'
echo -n '[*] Access your pricenode at http://'
cat "${TOR_RESOURCES}/${BISQ_TORHS}/hostname"
exit 0

View file

@ -1,20 +0,0 @@
#!/bin/bash
HOSTNAME="${COLLECTD_HOSTNAME:-localhost}"
INTERVAL=750
last=$(date +"%F %T" -d "$INTERVAL seconds ago")
while true;
do
now=$(date +"%F %T")
journalctl -u bisq-pricenode --since="$last" --until="$now" | grep -Eo "getAllMarketPrices.*bisq/[0-9].[0-9].[0-9]" | cut -d / -f 2 | sort | uniq -c | while read -r line; do
number=$(echo "${line}" | cut -d ' ' -f 1);
version=$(echo "${line}" | cut -d \ -f 2);
version=${version//./_};
echo "PUTVAL $HOSTNAME/requestsPer750Seconds/gauge-v$version interval=$INTERVAL N:$number";
done
last=$now
sleep $INTERVAL
done

View file

@ -1,20 +0,0 @@
#!/bin/bash
HOSTNAME="${COLLECTD_HOSTNAME:-localhost}"
INTERVAL=750
last=$(date +"%F %T" -d "$INTERVAL seconds ago")
while true;
do
now=$(date +"%F %T")
journalctl -u bisq-pricenode --since="$last" --until="$now" | grep -Eo "getAllMarketPrices.*HSv[0-9]" | grep -o "HSv[0-9]" | sort | uniq -c | while read -r line; do
number=$(echo "${line}" | cut -d ' ' -f 1);
version=$(echo "${line}" | cut -d \ -f 2);
version=${version//./_};
echo "PUTVAL $HOSTNAME/hsversionStats/gauge-$version interval=$INTERVAL N:$number";
done
last=$now
sleep $INTERVAL
done

View file

@ -1,51 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package 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

@ -1,35 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package 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

@ -1,120 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.price;
import bisq.common.UserThread;
import org.springframework.context.SmartLifecycle;
import java.time.Duration;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
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() {
return cachedResult;
}
@Override
public final void start() {
// do the initial refresh asynchronously
UserThread.runAfter(() -> {
try {
refresh();
} catch (Throwable t) {
log.warn("initial refresh failed", t);
}
}, 1, TimeUnit.MILLISECONDS);
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

@ -1,52 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package 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 minimumFee;
private final long timestamp;
public FeeRate(String currency, long price, long minimumFee, long timestamp) {
this.currency = currency;
this.price = price;
this.minimumFee = minimumFee;
this.timestamp = timestamp;
}
public String getCurrency() {
return currency;
}
public long getPrice() {
return price;
}
public long getMinimumFee() {
return minimumFee;
}
public long getTimestamp() {
return timestamp;
}
}

View file

@ -1,40 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package 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

@ -1,36 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package 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 static final long MIN_FEE_RATE_FOR_WITHDRAWAL = 1; // satoshi/vbyte
public static final long MIN_FEE_RATE_FOR_TRADING = 10; // satoshi/vbyte
public static final long MAX_FEE_RATE = 1000;
public FeeRateProvider(Duration refreshInterval) {
super(refreshInterval);
}
}

View file

@ -1,107 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.price.mining;
import bisq.common.config.Config;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* High-level mining {@link FeeRate} operations.
*/
@Service
public class FeeRateService {
private final List<FeeRateProvider> providers;
protected final Logger log = LoggerFactory.getLogger(this.getClass());
/**
* Construct a {@link FeeRateService} with a list of all {@link FeeRateProvider}
* implementations discovered via classpath scanning.
*
* @param providers all {@link FeeRateProvider} implementations in ascending
* order of precedence
*/
public FeeRateService(List<FeeRateProvider> providers) {
this.providers = providers;
}
public Map<String, Object> getFees() {
Map<String, Long> metadata = new HashMap<>();
Map<String, Long> allFeeRates = new HashMap<>();
AtomicLong sumOfAllFeeRates = new AtomicLong();
AtomicLong sumOfAllMinFeeRates = new AtomicLong();
AtomicInteger amountOfFeeRates = new AtomicInteger();
// Process each provider, retrieve and store their fee rate
providers.forEach(p -> {
FeeRate feeRate = p.get();
if (feeRate == null) {
log.warn("feeRate is null, provider={} ", p.toString());
return;
}
String currency = feeRate.getCurrency();
if ("BTC".equals(currency)) {
sumOfAllFeeRates.getAndAdd(feeRate.getPrice());
sumOfAllMinFeeRates.getAndAdd(feeRate.getMinimumFee());
amountOfFeeRates.getAndAdd(1);
}
});
// Calculate the average
long averageFeeRate = (amountOfFeeRates.intValue() > 0)
? sumOfAllFeeRates.longValue() / amountOfFeeRates.intValue()
: FeeRateProvider.MIN_FEE_RATE_FOR_TRADING;
long averageMinFeeRate = (amountOfFeeRates.intValue() > 0)
? sumOfAllMinFeeRates.longValue() / amountOfFeeRates.intValue()
: FeeRateProvider.MIN_FEE_RATE_FOR_WITHDRAWAL;
// Make sure the returned value is within the min-max range
averageFeeRate = Math.max(averageFeeRate, FeeRateProvider.MIN_FEE_RATE_FOR_TRADING);
averageFeeRate = Math.min(averageFeeRate, FeeRateProvider.MAX_FEE_RATE);
averageMinFeeRate = Math.max(averageMinFeeRate, FeeRateProvider.MIN_FEE_RATE_FOR_WITHDRAWAL);
averageMinFeeRate = Math.min(averageMinFeeRate, FeeRateProvider.MAX_FEE_RATE);
// Prepare response: Add timestamp of now
// Since this is an average, the timestamp is associated with when the moment in
// time when the avg was computed
metadata.put(Config.BTC_FEES_TS, Instant.now().getEpochSecond());
// Prepare response: Add the fee average
allFeeRates.put(Config.BTC_TX_FEE, averageFeeRate);
allFeeRates.put(Config.BTC_MIN_TX_FEE, averageMinFeeRate);
// Build response
return new HashMap<>() {{
putAll(metadata);
put(Config.LEGACY_FEE_DATAMAP, allFeeRates);
}};
}
}

View file

@ -1,228 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.price.mining.providers;
import bisq.price.PriceController;
import bisq.price.mining.FeeRate;
import bisq.price.mining.FeeRateProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Primary;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.annotation.Order;
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.Map;
import java.util.Optional;
import java.util.Set;
/**
* {@link FeeRateProvider} that interprets the Mempool API format to retrieve a mining
* fee estimate. See https://mempool.space.
*/
abstract class MempoolFeeRateProvider extends FeeRateProvider {
private static final int DEFAULT_MAX_BLOCKS = 2;
private static final int DEFAULT_REFRESH_INTERVAL = 2;
// Keys of properties defining the available Mempool API endpoints. To enable them,
// simply uncomment and adjust the corresponding lines in application.properties
private static final String MEMPOOL_HOSTNAME_KEY_1 = "bisq.price.mining.providers.mempoolHostname.1";
private static final String MEMPOOL_HOSTNAME_KEY_2 = "bisq.price.mining.providers.mempoolHostname.2";
private static final String MEMPOOL_HOSTNAME_KEY_3 = "bisq.price.mining.providers.mempoolHostname.3";
private static final String MEMPOOL_HOSTNAME_KEY_4 = "bisq.price.mining.providers.mempoolHostname.4";
private static final String MEMPOOL_HOSTNAME_KEY_5 = "bisq.price.mining.providers.mempoolHostname.5";
private static final RestTemplate restTemplate = new RestTemplate();
// TODO: As of the switch to the mempool.space API this field and related members are
// now dead code and should be removed, including removing the positional
// command-line argument from startup scripts. Operators need to be notified of this
// when it happens.
private final int maxBlocks;
protected Environment env;
public MempoolFeeRateProvider(Environment env) {
super(Duration.ofMinutes(refreshInterval(env)));
this.env = env;
this.maxBlocks = maxBlocks(env);
}
protected FeeRate doGet() {
// Default value is the minimum rate. If the connection to the fee estimate
// provider fails, we fall back to this value.
try {
return getEstimatedFeeRate();
}
catch (Exception e) {
// Something happened with the connection
log.error("Error retrieving bitcoin mining fee estimation: " + e.getMessage());
}
return new FeeRate("BTC", MIN_FEE_RATE_FOR_TRADING, MIN_FEE_RATE_FOR_WITHDRAWAL, Instant.now().getEpochSecond());
}
private FeeRate getEstimatedFeeRate() {
Set<Map.Entry<String, Long>> feeRatePredictions = getFeeRatePredictions();
long estimatedFeeRate = feeRatePredictions.stream()
.filter(p -> p.getKey().equalsIgnoreCase("halfHourFee"))
.map(Map.Entry::getValue)
.findFirst()
.map(r -> Math.max(r, MIN_FEE_RATE_FOR_TRADING))
.map(r -> Math.min(r, MAX_FEE_RATE))
.orElse(MIN_FEE_RATE_FOR_TRADING);
long economyFee = feeRatePredictions.stream()
.filter(p -> p.getKey().equalsIgnoreCase("economyFee"))
.map(Map.Entry::getValue)
.findFirst()
.orElse(MIN_FEE_RATE_FOR_WITHDRAWAL);
log.info("Retrieved estimated mining fee of {} sat/vB and economyFee of {} sat/vB from {}", estimatedFeeRate, economyFee, getMempoolApiHostname());
return new FeeRate("BTC", estimatedFeeRate, economyFee, Instant.now().getEpochSecond());
}
private Set<Map.Entry<String, Long>> getFeeRatePredictions() {
return restTemplate.exchange(
RequestEntity
.get(UriComponentsBuilder
// See https://github.com/bisq-network/projects/issues/27
.fromUriString("https://" + getMempoolApiHostname() + "/api/v1/fees/recommended")
.build().toUri())
.build(),
new ParameterizedTypeReference<Map<String, Long>>() { }
).getBody().entrySet();
}
/**
* Return the hostname of the fee estimation API endpoint. No prefix (https://), no
* suffix (trailing slashes, etc).
*/
protected abstract String getMempoolApiHostname();
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);
}
@Primary
@Component
@Order(1)
public static class First extends MempoolFeeRateProvider {
public First(Environment env) {
super(env);
}
protected String getMempoolApiHostname() {
// This is the primary instance, so if no API point is set in
// application.properties file, then it defaults to mempool.space
// This ensures there is at least one provider attempting to connect,
// even if the properties file is corrupt or empty
return env.getProperty(MEMPOOL_HOSTNAME_KEY_1, "mempool.space");
}
}
@Component
@Order(2)
@ConditionalOnProperty(name = MEMPOOL_HOSTNAME_KEY_2)
public static class Second extends MempoolFeeRateProvider {
public Second(Environment env) {
super(env);
}
protected String getMempoolApiHostname() {
return env.getProperty(MEMPOOL_HOSTNAME_KEY_2);
}
}
@Component
@Order(3)
@ConditionalOnProperty(name = MEMPOOL_HOSTNAME_KEY_3)
public static class Third extends MempoolFeeRateProvider {
public Third(Environment env) {
super(env);
}
protected String getMempoolApiHostname() {
return env.getProperty(MEMPOOL_HOSTNAME_KEY_3);
}
}
@Component
@Order(4)
@ConditionalOnProperty(name = MEMPOOL_HOSTNAME_KEY_4)
public static class Fourth extends MempoolFeeRateProvider {
public Fourth(Environment env) {
super(env);
}
protected String getMempoolApiHostname() {
return env.getProperty(MEMPOOL_HOSTNAME_KEY_4);
}
}
@Component
@Order(5)
@ConditionalOnProperty(name = MEMPOOL_HOSTNAME_KEY_5)
public static class Fifth extends MempoolFeeRateProvider {
public Fifth(Environment env) {
super(env);
}
protected String getMempoolApiHostname() {
return env.getProperty(MEMPOOL_HOSTNAME_KEY_5);
}
}
@RestController
class Controller extends PriceController {
@GetMapping(path = "/getParams")
public String getParams() {
return String.format("%s;%s", maxBlocks, refreshInterval.toMillis());
}
}
}

View file

@ -1,99 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package 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

@ -1,58 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.price.spot;
import bisq.price.PriceController;
import bisq.price.mining.FeeRateService;
import bisq.common.config.Config;
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;
private final FeeRateService feeRateService;
public ExchangeRateController(ExchangeRateService exchangeRateService, FeeRateService feeRateService) {
this.exchangeRateService = exchangeRateService;
this.feeRateService = feeRateService;
}
@GetMapping(path = "/getAllMarketPrices")
public Map<String, Object> getAllMarketPrices() {
Map<String, Object> retVal = exchangeRateService.getAllMarketPrices();
// add the fee info to results
feeRateService.getFees().forEach((key, value) -> {
retVal.put(translateFieldName(key), value);
});
return retVal;
}
static String translateFieldName(String name) {
if (name.equals(Config.LEGACY_FEE_DATAMAP))
name = Config.BTC_FEE_INFO; // name changed for clarity
return name;
}
}

View file

@ -1,338 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.price.spot;
import bisq.price.PriceProvider;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.TradeCurrency;
import org.knowm.xchange.Exchange;
import org.knowm.xchange.ExchangeFactory;
import org.knowm.xchange.currency.Currency;
import org.knowm.xchange.currency.CurrencyPair;
import org.knowm.xchange.dto.marketdata.Ticker;
import org.knowm.xchange.exceptions.ExchangeException;
import org.knowm.xchange.exceptions.NotYetImplementedForExchangeException;
import org.knowm.xchange.service.marketdata.MarketDataService;
import org.knowm.xchange.service.marketdata.params.CurrencyPairsParam;
import org.knowm.xchange.service.marketdata.params.Params;
import org.springframework.core.env.Environment;
import java.time.Duration;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 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. If multiple
* {@link ExchangeRateProvider}s retrieve rates for the same currency, then the
* {@link ExchangeRateService} will average them out and expose an aggregate rate.
*
* @see ExchangeRateService#getAllMarketPrices()
*/
public abstract class ExchangeRateProvider extends PriceProvider<Set<ExchangeRate>> {
private static Set<String> SUPPORTED_CRYPTO_CURRENCIES = new HashSet<>();
private static Set<String> SUPPORTED_FIAT_CURRENCIES = new HashSet<>();
private Set<String> providerExclusionList = new HashSet<>();
private final String name;
private final String prefix;
private final Environment env;
public ExchangeRateProvider(Environment env, String name, String prefix, Duration refreshInterval) {
super(refreshInterval);
this.name = name;
this.prefix = prefix;
this.env = env;
List<String> excludedByProvider =
Arrays.asList(env.getProperty("bisq.price.fiatcurrency.excludedByProvider", "")
.toUpperCase().trim().split("\\s*,\\s*"));
for (String s: excludedByProvider) {
String[] splits = s.split(":");
if (splits.length == 2 && splits[0].equalsIgnoreCase(name) && CurrencyUtil.isFiatCurrency(splits[1])) {
providerExclusionList.add(splits[1]);
}
}
if (providerExclusionList.size() > 0) {
log.info("{} specific exclusion list={}", name, providerExclusionList.toString());
}
}
public Set<String> getSupportedFiatCurrencies() {
if (SUPPORTED_FIAT_CURRENCIES.isEmpty()) { // one-time initialization
List<String> excludedFiatCurrencies =
Arrays.asList(env.getProperty("bisq.price.fiatcurrency.excluded", "")
.toUpperCase().trim().split("\\s*,\\s*"));
String validatedExclusionList = excludedFiatCurrencies.stream()
.filter(ccy -> !ccy.isEmpty())
.filter(CurrencyUtil::isFiatCurrency)
.collect(Collectors.toList()).toString();
SUPPORTED_FIAT_CURRENCIES = CurrencyUtil.getAllSortedFiatCurrencies().stream()
.map(TradeCurrency::getCode)
.filter(ccy -> !validatedExclusionList.contains(ccy.toUpperCase()))
.collect(Collectors.toSet());
log.info("fiat currencies excluded: {}", validatedExclusionList);
log.info("fiat currencies supported: {}", SUPPORTED_FIAT_CURRENCIES.size());
}
// filter out any provider specific ccy exclusions
return SUPPORTED_FIAT_CURRENCIES.stream()
.filter(ccy -> !providerExclusionList.contains(ccy.toUpperCase()))
.collect(Collectors.toSet());
}
public Set<String> getSupportedCryptoCurrencies() {
if (SUPPORTED_CRYPTO_CURRENCIES.isEmpty()) { // one-time initialization
List<String> excludedCryptoCurrencies =
Arrays.asList(env.getProperty("bisq.price.cryptocurrency.excluded", "")
.toUpperCase().trim().split("\\s*,\\s*"));
String validatedExclusionList = excludedCryptoCurrencies.stream()
.filter(ccy -> !ccy.isEmpty())
.filter(CurrencyUtil::isCryptoCurrency)
.collect(Collectors.toList()).toString();
SUPPORTED_CRYPTO_CURRENCIES = CurrencyUtil.getAllSortedCryptoCurrencies().stream()
.map(TradeCurrency::getCode)
.filter(ccy -> !validatedExclusionList.contains(ccy.toUpperCase()))
.collect(Collectors.toSet());
log.info("crypto currencies excluded: {}", validatedExclusionList);
log.info("crypto currencies supported: {}", SUPPORTED_CRYPTO_CURRENCIES.size());
}
return SUPPORTED_CRYPTO_CURRENCIES;
}
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()));
}
/**
* @param exchangeClass Class of the {@link Exchange} for which the rates should be
* polled
* @return Exchange rates for Bisq-supported fiat currencies and altcoins in the
* specified {@link Exchange}
*
* @see CurrencyUtil#getAllSortedFiatCurrencies()
* @see CurrencyUtil#getAllSortedCryptoCurrencies()
*/
protected Set<ExchangeRate> doGet(Class<? extends Exchange> exchangeClass) {
Set<ExchangeRate> result = new HashSet<ExchangeRate>();
// Initialize XChange objects
Exchange exchange = ExchangeFactory.INSTANCE.createExchange(exchangeClass.getName());
MarketDataService marketDataService = exchange.getMarketDataService();
// Retrieve all currency pairs supported by the exchange
List<CurrencyPair> allCurrencyPairsOnExchange = exchange.getExchangeSymbols();
// Find out which currency pairs we are interested in polling ("desired pairs")
// This will be the intersection of:
// 1) the pairs available on the exchange, and
// 2) the pairs Bisq considers relevant / valid
// This will result in two lists of desired pairs (fiat and alts)
// Find the desired fiat pairs (pair format is BTC-FIAT)
List<CurrencyPair> desiredFiatPairs = allCurrencyPairsOnExchange.stream()
.filter(cp -> cp.base.equals(Currency.BTC))
.filter(cp -> getSupportedFiatCurrencies().contains(cp.counter.getCurrencyCode()))
.collect(Collectors.toList());
// Find the desired altcoin pairs (pair format is ALT-BTC)
List<CurrencyPair> desiredCryptoPairs = allCurrencyPairsOnExchange.stream()
.filter(cp -> cp.counter.equals(Currency.BTC))
.filter(cp -> getSupportedCryptoCurrencies().contains(cp.base.getCurrencyCode()))
.collect(Collectors.toList());
// Retrieve in bulk all tickers offered by the exchange
// The benefits of this approach (vs polling each ticker) are twofold:
// 1) the polling of the exchange is faster (one HTTP call vs several)
// 2) it's easier to stay below any API rate limits the exchange might have
List<Ticker> tickersRetrievedFromExchange = new ArrayList<>();
try {
tickersRetrievedFromExchange = marketDataService.getTickers(new CurrencyPairsParam() {
/**
* The {@link MarketDataService#getTickers(Params)} interface requires a
* {@link CurrencyPairsParam} argument when polling for tickers in bulk.
* This parameter is meant to indicate a list of currency pairs for which
* the tickers should be polled. However, the actual implementations for
* the different exchanges differ, for example:
* - some will ignore it (and retrieve all available tickers)
* - some will require it (and will fail if a null or empty list is given)
* - some will properly handle it
*
* We take a simplistic approach, namely:
* - for providers that require such a filter, specify one
* - for all others, do not specify one
*
* We make this distinction using
* {@link ExchangeRateProvider#requiresFilterDuringBulkTickerRetrieval}
*
* @return Filter (list of desired currency pairs) to be used during bulk
* ticker retrieval
*/
@Override
public Collection<CurrencyPair> getCurrencyPairs() {
// If required by the exchange implementation, specify a filter
// (list of pairs which should be retrieved)
if (requiresFilterDuringBulkTickerRetrieval()) {
return Stream.of(desiredFiatPairs, desiredCryptoPairs)
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
// Otherwise, specify an empty list, indicating that the API should
// simply return all available tickers
return Collections.emptyList();
}
});
if (tickersRetrievedFromExchange.isEmpty()) {
// If the bulk ticker retrieval went through, but no tickers were
// retrieved, this is a strong indication that this specific exchange
// needs a specific list of pairs given as argument, for bulk retrieval to
// work. See requiresFilterDuringBulkTickerRetrieval()
throw new IllegalArgumentException("No tickers retrieved, " +
"exchange requires explicit filter argument during bulk retrieval?");
}
} catch (NotYetImplementedForExchangeException e) {
// Thrown when a provider has no marketDataService.getTickers() implementation
// either because the exchange API does not provide it, or because it has not
// been implemented yet in the knowm xchange library
// In this case (retrieval of bulk tickers is not possible) retrieve the
// tickers one by one
List<Ticker> finalTickersRetrievedFromExchange = tickersRetrievedFromExchange;
Stream.of(desiredFiatPairs, desiredCryptoPairs)
.flatMap(Collection::stream)
.collect(Collectors.toList())
.forEach(cp -> {
try {
// This is done in a loop, and can therefore result in a burst
// of API calls. Some exchanges do not allow bursts
// A simplistic solution is to delay every call by 1 second
// TODO Switch to using a more elegant solution (per exchange)
// like ResilienceSpecification (needs knowm xchange libs v5)
if (getMarketDataCallDelay() > 0) {
Thread.sleep(getMarketDataCallDelay());
}
Ticker ticker = marketDataService.getTicker(cp);
finalTickersRetrievedFromExchange.add(ticker);
} catch (IOException | InterruptedException ioException) {
ioException.printStackTrace();
log.error("Could not query tickers for " + getName(), e);
}
});
} catch (ExchangeException | // Errors reported by the exchange (rate limit, etc)
IOException | // Errors while trying to connect to the API (timeouts, etc)
// Potential error when integrating new exchange (hints that exchange
// provider implementation needs to overwrite
// requiresFilterDuringBulkTickerRetrieval() and have it return true )
IllegalArgumentException e) {
// Catch and handle all other possible exceptions
// If there was a problem with polling this exchange, return right away,
// since there are no results to parse and process
log.error("Could not query tickers for provider " + getName(), e);
return result;
}
// Create an ExchangeRate for each desired currency pair ticker that was retrieved
Predicate<Ticker> isDesiredFiatPair = t -> desiredFiatPairs.contains(t.getCurrencyPair());
Predicate<Ticker> isDesiredCryptoPair = t -> desiredCryptoPairs.contains(t.getCurrencyPair());
tickersRetrievedFromExchange.stream()
.filter(isDesiredFiatPair.or(isDesiredCryptoPair)) // Only consider desired pairs
.forEach(t -> {
// All tickers here match all requirements
// We have two kinds of currency pairs, BTC-FIAT and ALT-BTC
// In the first one, BTC is the first currency of the pair
// In the second type, BTC is listed as the second currency
// Distinguish between the two and create ExchangeRates accordingly
// In every Bisq ExchangeRate, BTC is one currency in the pair
// Extract the other currency from the ticker, to create ExchangeRates
String otherExchangeRateCurrency;
if (t.getCurrencyPair().base.equals(Currency.BTC)) {
otherExchangeRateCurrency = t.getCurrencyPair().counter.getCurrencyCode();
} else {
otherExchangeRateCurrency = t.getCurrencyPair().base.getCurrencyCode();
}
result.add(new ExchangeRate(
otherExchangeRateCurrency,
t.getLast(),
// Some exchanges do not provide timestamps
t.getTimestamp() == null ? new Date() : t.getTimestamp(),
this.getName()
));
});
return result;
}
/**
* Specifies optional delay between certain kind of API calls that can result in
* bursts. We want to avoid bursts, because this can cause certain exchanges to
* temporarily restrict access to the pricenode IP.
*
* @return Amount of milliseconds of delay between marketDataService.getTicker calls.
* By default 0, but can be overwritten by each provider.
*/
protected long getMarketDataCallDelay() {
return 0;
}
/**
* @return Whether or not the bulk retrieval of tickers from the exchange requires an
* explicit filter (list of desired pairs) or not. If true, the
* {@link MarketDataService#getTickers(Params)} call will be constructed and given as
* argument, which acts as a filter indicating for which pairs the ticker should be
* retrieved. If false, {@link MarketDataService#getTickers(Params)} will be called
* with an empty argument, indicating that the API should simply return all available
* tickers on the exchange
*/
protected boolean requiresFilterDuringBulkTickerRetrieval() {
return false;
}
}

View file

@ -1,187 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.price.spot;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.OptionalDouble;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.util.Arrays.asList;
/**
* 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> aggregateExchangeRates = getAggregateExchangeRates();
providers.forEach(p -> {
if (p.get() == null)
return;
Set<ExchangeRate> exchangeRates = p.get();
// Specific metadata fields for specific providers are expected by the client,
// mostly for historical reasons
// Therefore, add metadata fields for all known providers
// Rates are encapsulated in the "data" map below
metadata.putAll(getMetadata(p, exchangeRates));
});
LinkedHashMap<String, Object> result = new LinkedHashMap<>();
result.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<>(aggregateExchangeRates.values());
values.sort(Comparator.comparing(ExchangeRate::getCurrency));
result.put("data", values);
return result;
}
/**
* For each currency, create an aggregate {@link ExchangeRate} based on the currency's
* rates from all providers. If multiple providers have rates for the currency, then
* aggregate price = average of retrieved prices. If a single provider has rates for
* the currency, then aggregate price = the rate from that provider.
*
* @return Aggregate {@link ExchangeRate}s based on info from all providers, indexed
* by currency code
*/
private Map<String, ExchangeRate> getAggregateExchangeRates() {
Map<String, ExchangeRate> aggregateExchangeRates = new HashMap<>();
// Query all providers and collect all exchange rates, grouped by currency code
// key = currency code
// value = list of exchange rates
Map<String, List<ExchangeRate>> currencyCodeToExchangeRates = getCurrencyCodeToExchangeRates();
// For each currency code, calculate aggregate rate
currencyCodeToExchangeRates.forEach((currencyCode, exchangeRateList) -> {
if (exchangeRateList.isEmpty()) {
// If the map was built incorrectly and this currency points to an empty
// list of rates, skip it
return;
}
ExchangeRate aggregateExchangeRate;
if (exchangeRateList.size() == 1) {
// If a single provider has rates for this currency, then aggregate = rate
// from that provider
aggregateExchangeRate = exchangeRateList.get(0);
} else {
// If multiple providers have rates for this currency, then
// aggregate = average of the rates
OptionalDouble opt = exchangeRateList.stream().mapToDouble(ExchangeRate::getPrice).average();
// List size > 1, so opt is always set
double priceAvg = opt.orElseThrow(IllegalStateException::new);
aggregateExchangeRate = new ExchangeRate(
currencyCode,
BigDecimal.valueOf(priceAvg),
new Date(), // timestamp = time when avg is calculated
"Bisq-Aggregate");
}
aggregateExchangeRates.put(aggregateExchangeRate.getCurrency(), aggregateExchangeRate);
});
return aggregateExchangeRates;
}
/**
* @return All {@link ExchangeRate}s from all providers, grouped by currency code
*/
private Map<String, List<ExchangeRate>> getCurrencyCodeToExchangeRates() {
Map<String, List<ExchangeRate>> currencyCodeToExchangeRates = new HashMap<>();
for (ExchangeRateProvider p : providers) {
if (p.get() == null)
continue;
for (ExchangeRate exchangeRate : p.get()) {
String currencyCode = exchangeRate.getCurrency();
if (currencyCodeToExchangeRates.containsKey(currencyCode)) {
List<ExchangeRate> l = new ArrayList<>(currencyCodeToExchangeRates.get(currencyCode));
l.add(exchangeRate);
currencyCodeToExchangeRates.put(currencyCode, l);
} else {
currencyCodeToExchangeRates.put(currencyCode, asList(exchangeRate));
}
}
}
return currencyCodeToExchangeRates;
}
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());
if (log.isDebugEnabled())
t.printStackTrace();
}
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

@ -1,46 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.btcmarkets.BTCMarketsExchange;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class BTCMarkets extends ExchangeRateProvider {
public BTCMarkets(Environment env) {
super(env, "BTCMARKETS", "btcmarkets", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: AUD
// Supported alts: ETH, LTC
return doGet(BTCMarketsExchange.class);
}
}

View file

@ -1,46 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.binance.BinanceExchange;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class Binance extends ExchangeRateProvider {
public Binance(Environment env) {
super(env, "BINANCE", "binance", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: EUR, GBP, NGN, RUB, TRY, UAH, ZAR
// Supported alts: BEAM, DAI, DASH, DCR, DOGE, ETC, ETH, LTC, NAV, PIVX, XMR, XZC,
// ZEC, ZEN
return doGet(BinanceExchange.class);
}
}

View file

@ -1,57 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.HashSet;
import java.util.Set;
/**
* Stub implementation (similar to #CoinMarketCap) for backward compatibility with legacy
* Bisq clients
*/
@Component
class BitcoinAverage extends ExchangeRateProvider {
public BitcoinAverage(Environment env) {
// Simulate a deactivated BitcoinAverage provider
// We still need the class to exist and be registered as a provider though,
// because the returned data structure must contain the "btcAverageTs" key
// for backward compatibility with Bisq clients which hardcode that key
super(env, "BA", "btcAverage", Duration.ofMinutes(100));
}
/**
* @see CoinMarketCap#doGet()
* @return
*/
@Override
public Set<ExchangeRate> doGet() {
HashSet<ExchangeRate> exchangeRates = new HashSet<>();
exchangeRates.add(new ExchangeRate("NON_EXISTING_SYMBOL_BA", 0, 0L, getName()));
return exchangeRates;
}
}

View file

@ -1,45 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.bitfinex.BitfinexExchange;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class Bitfinex extends ExchangeRateProvider {
public Bitfinex(Environment env) {
super(env, "BITFINEX", "bitfinex", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: EUR, GBP, JPY, USD
// Supported alts: DAI, ETC, ETH, LTC, XMR, ZEC
return doGet(BitfinexExchange.class);
}
}

View file

@ -1,45 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.bitflyer.BitflyerExchange;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class Bitflyer extends ExchangeRateProvider {
public Bitflyer(Environment env) {
super(env, "BITFLYER", "bitflyer", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: JPY
// Supported alts: ETH
return doGet(BitflyerExchange.class);
}
}

View file

@ -1,45 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.bitstamp.BitstampExchange;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class Bitstamp extends ExchangeRateProvider {
public Bitstamp(Environment env) {
super(env, "BITSTAMP", "bitstamp", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: EUR, GBP, USD
// Supported alts: ETH, LTC
return doGet(BitstampExchange.class);
}
}

View file

@ -1,105 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import bisq.price.util.coingecko.CoinGeckoMarketData;
import org.springframework.core.ParameterizedTypeReference;
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 java.time.Duration;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@Component
class CoinGecko extends ExchangeRateProvider {
private final RestTemplate restTemplate = new RestTemplate();
public CoinGecko(Environment env) {
super(env, "COINGECKO", "coingecko", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Rate limit for the CoinGecko API is 10 calls each second per IP address
// We retrieve all rates in bulk, so we only make 1 call per provider poll
Set<ExchangeRate> result = new HashSet<ExchangeRate>();
Predicate<Map.Entry> isDesiredFiatPair = t -> getSupportedFiatCurrencies().contains(t.getKey());
Predicate<Map.Entry> isDesiredCryptoPair = t -> getSupportedCryptoCurrencies().contains(t.getKey());
getMarketData().getRates().entrySet().stream()
.filter(isDesiredFiatPair.or(isDesiredCryptoPair))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
.forEach((key, ticker) -> {
boolean useInverseRate = false;
if (getSupportedCryptoCurrencies().contains(key)) {
// Use inverse rate for alts, because the API returns the
// conversion rate in the opposite direction than what we need
// API returns the BTC/Alt rate, we need the Alt/BTC rate
useInverseRate = true;
}
BigDecimal rate = ticker.getValue();
// Find the inverse rate, while using enough decimals to reflect very
// small exchange rates
BigDecimal inverseRate = (rate.compareTo(BigDecimal.ZERO) > 0) ?
BigDecimal.ONE.divide(rate, 8, RoundingMode.HALF_UP) :
BigDecimal.ZERO;
result.add(new ExchangeRate(
key,
(useInverseRate ? inverseRate : rate),
new Date(),
this.getName()
));
});
return result;
}
private CoinGeckoMarketData getMarketData() {
return restTemplate.exchange(
RequestEntity
.get(UriComponentsBuilder
.fromUriString("https://api.coingecko.com/api/v3/exchange_rates").build()
.toUri())
.build(),
new ParameterizedTypeReference<CoinGeckoMarketData>() {
}
).getBody();
}
}

View file

@ -1,54 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.HashSet;
import java.util.Set;
/**
* Stub implementation of CoinMarketCap price provider to prevent NullPointerExceptions within legacy clients
*/
@Component
class CoinMarketCap extends ExchangeRateProvider {
public CoinMarketCap(Environment env) {
super(env, "CMC", "coinmarketcap", Duration.ofMinutes(5)); // large data structure, so don't request it too often
}
/**
* Returns a Set with a non existing symbol for the CoinMarketCap price provider.
* Price data of CMC provider is not used in the client anymore, except for the last update timestamp.
* To prevent a unnecessary warning log in that case we have to pass at least one element.
*
* @return Empty Set
*/
@Override
public Set<ExchangeRate> doGet() {
HashSet<ExchangeRate> exchangeRates = new HashSet<>();
exchangeRates.add(new ExchangeRate("NON_EXISTING_SYMBOL", 0, 0L, "CMC"));
return exchangeRates;
}
}

View file

@ -1,49 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.coinbasepro.CoinbaseProExchange;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class CoinbasePro extends ExchangeRateProvider {
public CoinbasePro(Environment env) {
super(env, "COINBASEPRO", "coinbasepro", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: EUR, USD, GBP
// Supported alts: DASH, DOGE, ETC, ETH, LTC, ZEC, ZEN
return doGet(CoinbaseProExchange.class);
}
@Override
protected boolean requiresFilterDuringBulkTickerRetrieval() {
return true;
}
}

View file

@ -1,46 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.coinone.CoinoneExchange;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class Coinone extends ExchangeRateProvider {
public Coinone(Environment env) {
super(env, "COINONE", "coinone", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: KRW
// Supported alts: -
return doGet(CoinoneExchange.class);
}
}

View file

@ -1,46 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.independentreserve.IndependentReserveExchange;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class IndependentReserve extends ExchangeRateProvider {
public IndependentReserve(Environment env) {
super(env, "IndependentReserve", "independentreserve", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: AUD, NZD (New Zealand Dollar), USD
// Supported alts: -
return doGet(IndependentReserveExchange.class);
}
}

View file

@ -1,50 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.kraken.KrakenExchange;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class Kraken extends ExchangeRateProvider {
public Kraken(Environment env) {
super(env, "KRAKEN", "kraken", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: AUD, CAD, CHF, EUR, GBP, JPY, USD
// Supported alts: DASH, DOGE, ETC, ETH, LTC, XMR, ZEC
return doGet(KrakenExchange.class);
}
@Override
protected boolean requiresFilterDuringBulkTickerRetrieval() {
return true;
}
}

View file

@ -1,53 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.luno.LunoExchange;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class Luno extends ExchangeRateProvider {
public Luno(Environment env) {
super(env, "LUNO", "luno", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: IDR (Indonesian rupiah), MYR (Malaysian ringgit),
// NGN (Nigerian Naira), ZAR (South African rand)
// Supported alts: -
return doGet(LunoExchange.class);
}
@Override
protected long getMarketDataCallDelay() {
// Luno allows only 1 MarketData call per second
// (see https://www.luno.com/en/developers/api )
return 1000;
}
}

View file

@ -1,46 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.mercadobitcoin.MercadoBitcoinExchange;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class MercadoBitcoin extends ExchangeRateProvider {
public MercadoBitcoin(Environment env) {
super(env, "MercadoBitcoin", "mercadobitcoin", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: BRL (Brazilian Real)
// Supported alts: -
return doGet(MercadoBitcoinExchange.class);
}
}

View file

@ -1,45 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.paribu.ParibuExchange;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class Paribu extends ExchangeRateProvider {
public Paribu(Environment env) {
super(env, "PARIBU", "paribu", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: TRY (Turkish Lira)
// Supported alts: -
return doGet(ParibuExchange.class);
}
}

View file

@ -1,45 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.poloniex.PoloniexExchange;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class Poloniex extends ExchangeRateProvider {
public Poloniex(Environment env) {
super(env, "POLO", "poloniex", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: -
// Supported alts: DASH, DCR, DOGE, ETC, ETH, LTC, XMR, ZEC
return doGet(PoloniexExchange.class);
}
}

View file

@ -1,50 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.price.spot.providers;
import bisq.price.spot.ExchangeRate;
import bisq.price.spot.ExchangeRateProvider;
import org.knowm.xchange.quoine.QuoineExchange;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Set;
@Component
class Quoine extends ExchangeRateProvider {
public Quoine(Environment env) {
super(env, "QUOINE", "quoine", Duration.ofMinutes(1));
}
@Override
public Set<ExchangeRate> doGet() {
// Supported fiat: AUD, CNY, EUR, HKD, IDR, INR, JPY, PHP, SGD, USD
// Supported alts: ETH
return doGet(QuoineExchange.class);
}
@Override
protected boolean requiresFilterDuringBulkTickerRetrieval() {
return true;
}
}

View file

@ -1,32 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package 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

@ -1,13 +0,0 @@
package bisq.price.util.bitpay;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class BitpayMarketData {
private BitpayTicker[] data;
}

View file

@ -1,18 +0,0 @@
package bisq.price.util.bitpay;
import java.math.BigDecimal;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class BitpayTicker {
private String code;
private String name;
private BigDecimal rate;
}

View file

@ -1,22 +0,0 @@
package bisq.price.util.coingecko;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class CoinGeckoMarketData {
private Map<String, CoinGeckoTicker> rates;
public void setRates(Map<String, CoinGeckoTicker> rates) {
// Convert keys to uppercase ("usd" -> "USD") when deserializing API response
this.rates = rates.entrySet().stream()
.collect(Collectors.toMap(entry -> entry.getKey().toUpperCase(), entry -> entry.getValue()));
}
}

View file

@ -1,20 +0,0 @@
package bisq.price.util.coingecko;
import java.math.BigDecimal;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class CoinGeckoTicker {
private String name;
private String unit;
private BigDecimal value;
private String type;
}

View file

@ -1,17 +0,0 @@
package bisq.price.util.coinpaprika;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class CoinpaprikaMarketData {
// All other json fields can be ignored, we don't need them
private Map<String, CoinpaprikaTicker> quotes;
}

View file

@ -1,16 +0,0 @@
package bisq.price.util.coinpaprika;
import java.math.BigDecimal;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class CoinpaprikaTicker {
// All other json fields can be ignored, we don't need them
private BigDecimal price;
}

View file

@ -1,12 +0,0 @@
spring.jackson.serialization.indent_output=true
# To enable another fee estimation endpoint, simply uncomment one of the following lines
# and set it to hostname exposing the fee estimation API
bisq.price.mining.providers.mempoolHostname.1=mempool.space
bisq.price.mining.providers.mempoolHostname.2=mempool.emzy.de
bisq.price.mining.providers.mempoolHostname.3=mempool.ninja
bisq.price.mining.providers.mempoolHostname.4=mempool.bisq.services
# bisq.price.mining.providers.mempoolHostname.5=someHostOrIP
bisq.price.fiatcurrency.excluded=LBP,ARS
bisq.price.fiatcurrency.excludedByProvider=HUOBI:BRL
bisq.price.cryptocurrency.excluded=

View file

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

View file

@ -1,16 +0,0 @@
<?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>

Some files were not shown because too many files have changed in this diff Show more