Merge pull request #7153 from HenrikJannsen/add-bitcoin-monitor

Add bitcoin monitor
This commit is contained in:
Alejandro García 2024-06-11 23:32:27 +00:00 committed by GitHub
commit e6338af65b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 1393 additions and 12 deletions

25
btcnodemonitor/README.md Normal file
View File

@ -0,0 +1,25 @@
## Bitcoin node monitor
This is a simple headless node with a http server which connects periodically to the Bisq-provided Bitcoin nodes and
disconnect quickly afterwards.
### Run Bitcoin node monitor
Run the Gradle task:
```sh
./gradlew btcnodemonitor:run
```
Or create a run scrip by:
```sh
./gradlew btcnodemonitor:startBisqApp
```
And then run:
```sh
./bisq-btcnodemonitor
```

View File

@ -0,0 +1,37 @@
plugins {
id 'bisq.application'
id 'bisq.gradle.app_start_plugin.AppStartPlugin'
}
mainClassName = 'bisq.btcnodemonitor.BtcNodeMonitorMain'
distTar.enabled = true
dependencies {
implementation enforcedPlatform(project(':platform'))
implementation project(':proto')
implementation project(':common')
implementation project(':core')
implementation project(':p2p')
annotationProcessor libs.lombok
compileOnly libs.javax.annotation
compileOnly libs.lombok
implementation libs.logback.classic
implementation libs.logback.core
implementation libs.google.guava
implementation libs.apache.commons.lang3
implementation libs.jetbrains.annotations
implementation libs.slf4j.api
implementation(libs.netlayer.tor.external) {
exclude(module: 'slf4j-api')
}
implementation(libs.bitcoinj) {
exclude(module: 'bcprov-jdk15on')
exclude(module: 'guava')
exclude(module: 'jsr305')
exclude(module: 'okhttp')
exclude(module: 'okio')
exclude(module: 'slf4j-api')
}
implementation libs.spark.core
}

View File

@ -0,0 +1,66 @@
/*
* 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.btcnodemonitor;
import bisq.core.btc.nodes.BtcNodes;
import bisq.common.config.Config;
import java.util.concurrent.CompletableFuture;
import lombok.extern.slf4j.Slf4j;
import bisq.btcnodemonitor.btc.PeerConncetionModel;
import bisq.btcnodemonitor.btc.PeerGroupService;
import bisq.btcnodemonitor.server.SimpleHttpServer;
import bisq.btcnodemonitor.socksProxy.ProxySetup;
@Slf4j
public class BtcNodeMonitor {
private final PeerGroupService peerGroupService;
private final ProxySetup proxySetup;
private final SimpleHttpServer simpleHttpServer;
public BtcNodeMonitor(Config config) {
PeerConncetionModel peerConncetionModel = new PeerConncetionModel(new BtcNodes().getProvidedBtcNodes(), this::onChange);
simpleHttpServer = new SimpleHttpServer(config, peerConncetionModel);
proxySetup = new ProxySetup(config);
peerGroupService = new PeerGroupService(config, peerConncetionModel);
}
public void onChange() {
simpleHttpServer.onChange();
}
public CompletableFuture<Void> start() {
return simpleHttpServer.start()
.thenCompose(nil -> proxySetup.createSocksProxy())
.thenAccept(peerGroupService::applySocks5Proxy)
.thenCompose(nil -> peerGroupService.start())
.thenRun(peerGroupService::connectToAll);
}
public CompletableFuture<Void> shutdown() {
return peerGroupService.shutdown()
.thenCompose(nil -> proxySetup.shutdown())
.thenCompose(nil -> simpleHttpServer.shutdown());
}
}

View File

@ -0,0 +1,73 @@
/*
* 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.btcnodemonitor;
import bisq.common.UserThread;
import bisq.common.config.Config;
import bisq.common.handlers.ResultHandler;
import bisq.common.setup.CommonSetup;
import bisq.common.setup.GracefulShutDownHandler;
import bisq.common.util.SingleThreadExecutorUtils;
import bisq.common.util.Utilities;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class BtcNodeMonitorMain implements GracefulShutDownHandler {
public static void main(String[] args) {
new BtcNodeMonitorMain(args);
}
private final Config config;
@Getter
private final BtcNodeMonitor btcNodeMonitor;
public BtcNodeMonitorMain(String[] args) {
config = new Config("bisq_btc_node_monitor", Utilities.getUserDataDir(), args);
CommonSetup.setup(config, this);
configUserThread();
btcNodeMonitor = new BtcNodeMonitor(config);
btcNodeMonitor.start().join();
keepRunning();
}
@Override
public void gracefulShutDown(ResultHandler resultHandler) {
btcNodeMonitor.shutdown().join();
System.exit(0);
resultHandler.handleResult();
}
private void keepRunning() {
try {
Thread.currentThread().setName("BtcNodeMonitorMain");
Thread.currentThread().join();
} catch (InterruptedException e) {
log.error("BtcNodeMonitorMain Thread interrupted", e);
gracefulShutDown(() -> {
});
}
}
private void configUserThread() {
UserThread.setExecutor(SingleThreadExecutorUtils.getSingleThreadExecutor("UserThread"));
}
}

View File

@ -0,0 +1,191 @@
/*
* 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.btcnodemonitor.btc;
import org.bitcoinj.core.Peer;
import org.bitcoinj.core.PeerAddress;
import org.bitcoinj.core.VersionMessage;
import com.google.common.base.Joiner;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.Setter;
@Getter
public class PeerConncetionInfo {
private final List<ConnectionAttempt> connectionAttempts = new ArrayList<>();
private final PeerAddress peerAddress;
private final Runnable onChangeHandler;
private Optional<ConnectionAttempt> currentConnectionAttempt = Optional.empty();
public PeerConncetionInfo(PeerAddress peerAddress, Runnable onChangeHandler) {
this.peerAddress = peerAddress;
this.onChangeHandler = onChangeHandler;
}
public ConnectionAttempt newConnectionAttempt(Peer peer) {
currentConnectionAttempt = Optional.of(new ConnectionAttempt(peer, onChangeHandler));
connectionAttempts.add(currentConnectionAttempt.get());
onChangeHandler.run();
return currentConnectionAttempt.get();
}
public String getAddress() {
InetAddress inetAddress = peerAddress.getAddr();
if (inetAddress != null) {
return inetAddress.getHostAddress();
} else {
return peerAddress.getHostname();
}
}
public String getShortId() {
return getAddress().substring(0, 12) + "...";
}
public int getNumConnectionAttempts() {
return connectionAttempts.size();
}
public int getNumConnections() {
return (int) connectionAttempts.stream().filter(e -> e.isConnected).count();
}
public int getNumDisconnections() {
return (int) connectionAttempts.stream().filter(e -> !e.isConnected).count();
}
public int getNumFailures() {
return (int) connectionAttempts.stream().filter(e -> e.exception.isPresent()).count();
}
public int getNumSuccess() {
return (int) connectionAttempts.stream().filter(e -> e.versionMessage.isPresent()).count();
}
public List<ConnectionAttempt> getReverseConnectionAttempts() {
List<ConnectionAttempt> reverseConnectionAttempts = new ArrayList<>(connectionAttempts);
Collections.reverse(reverseConnectionAttempts);
return reverseConnectionAttempts;
}
public Optional<ConnectionAttempt> getLastSuccessfulConnected() {
return getReverseConnectionAttempts().stream().filter(e -> e.versionMessage.isPresent()).findFirst();
}
public int getIndex(ConnectionAttempt connectionAttempt) {
return connectionAttempts.indexOf(connectionAttempt);
}
public long getLastSuccessfulConnectTime() {
return getReverseConnectionAttempts().stream().filter(e -> e.versionMessage.isPresent()).findFirst()
.map(ConnectionAttempt::getDurationUntilConnection)
.orElse(0L);
}
public double getAverageTimeToConnect() {
return connectionAttempts.stream().mapToLong(ConnectionAttempt::getDurationUntilConnection).average().orElse(0d);
}
public Optional<String> getLastExceptionMessage() {
return getLastAttemptWithException()
.flatMap(ConnectionAttempt::getException)
.map(Throwable::getMessage);
}
public Optional<ConnectionAttempt> getLastAttemptWithException() {
return getReverseConnectionAttempts().stream()
.filter(e -> e.exception.isPresent())
.findFirst();
}
public String getAllExceptionMessages() {
return Joiner.on(",\n")
.join(getReverseConnectionAttempts().stream()
.filter(e -> e.exception.isPresent())
.flatMap(e -> e.getException().stream())
.map(Throwable::getMessage)
.collect(Collectors.toList()));
}
public double getFailureRate() {
if (getNumConnectionAttempts() == 0) {
return 0;
}
return getNumFailures() / (double) getNumConnectionAttempts();
}
@Override
public String toString() {
return getShortId();
}
@Getter
public static class ConnectionAttempt {
private final Peer peer;
private final Runnable updateHandler;
private final long connectTs;
private boolean isConnected;
@Setter
private long connectionStartedTs;
@Setter
private long connectionSuccessTs;
@Setter
private long durationUntilConnection;
@Setter
private long durationUntilDisConnection;
@Setter
private long durationUntilFailure;
private Optional<Throwable> exception = Optional.empty();
private Optional<VersionMessage> versionMessage = Optional.empty();
public ConnectionAttempt(Peer peer, Runnable updateHandler) {
this.peer = peer;
this.updateHandler = updateHandler;
connectTs = System.currentTimeMillis();
}
public void onConnected() {
// We clone to avoid change of fields when disconnect happens
VersionMessage peerVersionMessage = peer.getPeerVersionMessage().duplicate();
versionMessage = Optional.of(peerVersionMessage);
isConnected = true;
updateHandler.run();
}
public void onDisconnected() {
isConnected = false;
updateHandler.run();
}
public void onException(Throwable exception) {
this.exception = Optional.of(exception);
isConnected = false;
updateHandler.run();
}
}
}

View File

@ -0,0 +1,49 @@
/*
* 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.btcnodemonitor.btc;
import bisq.core.btc.nodes.BtcNodes;
import org.bitcoinj.core.PeerAddress;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.Getter;
@Getter
public class PeerConncetionModel {
private final Map<String, PeerConncetionInfo> map = new HashMap<>();
private final List<BtcNodes.BtcNode> providedBtcNodes;
private final Runnable onChangeHandler;
public PeerConncetionModel(List<BtcNodes.BtcNode> providedBtcNodes, Runnable onChangeHandler) {
this.providedBtcNodes = providedBtcNodes;
this.onChangeHandler = onChangeHandler;
}
public void fill(Set<PeerAddress> peerAddresses) {
map.clear();
map.putAll(peerAddresses.stream()
.map(peerAddress -> new PeerConncetionInfo(peerAddress, onChangeHandler))
.collect(Collectors.toMap(PeerConncetionInfo::getAddress, e -> e)));
}
}

View File

@ -0,0 +1,196 @@
/*
* 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.btcnodemonitor.btc;
import bisq.common.Timer;
import bisq.common.UserThread;
import bisq.common.util.SingleThreadExecutorUtils;
import org.bitcoinj.core.Context;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Peer;
import org.bitcoinj.core.PeerAddress;
import org.bitcoinj.core.Utils;
import org.bitcoinj.core.VersionMessage;
import org.bitcoinj.net.BlockingClientManager;
import org.bitcoinj.net.ClientConnectionManager;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.net.SocketAddress;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
@Slf4j
public class PeerConnection {
private final Context context;
private final int disconnectIntervalSec;
private final int reconnectIntervalSec;
private final ClientConnectionManager clientConnectionManager;
private final NetworkParameters params;
private final int connectTimeoutMillis;
private final int vMinRequiredProtocolVersion;
private final PeerConncetionInfo peerConncetionInfo;
private Optional<Timer> disconnectScheduler = Optional.empty();
private Optional<Timer> reconnectScheduler = Optional.empty();
private final AtomicBoolean shutdownCalled = new AtomicBoolean();
public PeerConnection(Context context,
PeerConncetionInfo peerConncetionInfo,
BlockingClientManager blockingClientManager,
int connectTimeoutMillis,
int disconnectIntervalSec,
int reconnectIntervalSec) {
this.context = context;
this.peerConncetionInfo = peerConncetionInfo;
this.clientConnectionManager = blockingClientManager;
this.connectTimeoutMillis = connectTimeoutMillis;
this.disconnectIntervalSec = disconnectIntervalSec;
this.reconnectIntervalSec = reconnectIntervalSec;
this.params = context.getParams();
vMinRequiredProtocolVersion = params.getProtocolVersionNum(NetworkParameters.ProtocolVersion.BLOOM_FILTER);
}
public CompletableFuture<Void> shutdown() {
shutdownCalled.set(true);
disconnectScheduler.ifPresent(Timer::stop);
reconnectScheduler.ifPresent(Timer::stop);
return CompletableFuture.runAsync(() -> {
log.info("shutdown {}", peerConncetionInfo);
Context.propagate(context);
disconnect();
}, SingleThreadExecutorUtils.getSingleThreadExecutor("shutdown-" + peerConncetionInfo.getShortId()));
}
public void connect() {
CompletableFuture.runAsync(() -> {
log.info("connect {}", peerConncetionInfo);
Context.propagate(context);
Peer peer = createPeer(peerConncetionInfo.getPeerAddress());
PeerConncetionInfo.ConnectionAttempt connectionAttempt = peerConncetionInfo.newConnectionAttempt(peer);
long ts = System.currentTimeMillis();
connectionAttempt.setConnectionStartedTs(ts);
try {
peer.addConnectedEventListener((peer1, peerCount) -> {
connectionAttempt.setDurationUntilConnection(System.currentTimeMillis() - ts);
connectionAttempt.setConnectionSuccessTs(System.currentTimeMillis());
connectionAttempt.onConnected();
startAutoDisconnectAndReconnect();
});
peer.addDisconnectedEventListener((peer1, peerCount) -> {
long passed = System.currentTimeMillis() - ts;
// Timeout is not handled as error in bitcoinj, but it simply disconnects
// If we had a successful connect before we got versionMessage set, otherwise its from an error.
if (connectionAttempt.getVersionMessage().isEmpty()) {
connectionAttempt.setDurationUntilFailure(passed);
connectionAttempt.onException(new RuntimeException("Connection failed"));
} else {
connectionAttempt.setDurationUntilDisConnection(passed);
connectionAttempt.onDisconnected();
}
startAutoDisconnectAndReconnect();
});
openConnection(peer).join();
} catch (Exception exception) {
log.warn("Error at opening connection to peer {}", peerConncetionInfo, exception);
connectionAttempt.setDurationUntilFailure(System.currentTimeMillis() - ts);
connectionAttempt.onException(exception);
startAutoDisconnectAndReconnect();
}
}, SingleThreadExecutorUtils.getSingleThreadExecutor("connect-" + peerConncetionInfo.getShortId()));
}
private CompletableFuture<Void> disconnect() {
return peerConncetionInfo.getCurrentConnectionAttempt()
.map(currentConnectionAttempt -> CompletableFuture.runAsync(() -> {
log.info("disconnect {}", peerConncetionInfo);
Context.propagate(context);
currentConnectionAttempt.getPeer().close();
},
SingleThreadExecutorUtils.getSingleThreadExecutor("disconnect-" + peerConncetionInfo.getShortId())))
.orElse(CompletableFuture.completedFuture(null));
}
private void startAutoDisconnectAndReconnect() {
if (shutdownCalled.get()) {
return;
}
disconnectScheduler.ifPresent(Timer::stop);
disconnectScheduler = Optional.of(UserThread.runAfter(() -> {
if (shutdownCalled.get()) {
return;
}
disconnect()
.thenRun(() -> {
if (shutdownCalled.get()) {
return;
}
reconnectScheduler.ifPresent(Timer::stop);
reconnectScheduler = Optional.of(UserThread.runAfter(() -> {
if (shutdownCalled.get()) {
return;
}
connect();
}, reconnectIntervalSec));
});
}, disconnectIntervalSec));
}
private Peer createPeer(PeerAddress address) {
Peer peer = new Peer(params, getVersionMessage(address), address, null, 0, 0);
peer.setMinProtocolVersion(vMinRequiredProtocolVersion);
peer.setSocketTimeout(connectTimeoutMillis);
return peer;
}
private CompletableFuture<SocketAddress> openConnection(Peer peer) {
CompletableFuture<SocketAddress> future = new CompletableFuture<>();
ListenableFuture<SocketAddress> listenableFuture = clientConnectionManager.openConnection(peer.getAddress().toSocketAddress(), peer);
Futures.addCallback(listenableFuture, new FutureCallback<>() {
@Override
public void onSuccess(SocketAddress result) {
future.complete(result);
}
@Override
public void onFailure(@NotNull Throwable throwable) {
future.completeExceptionally(throwable);
}
}, MoreExecutors.directExecutor());
return future;
}
private VersionMessage getVersionMessage(PeerAddress address) {
VersionMessage versionMessage = new VersionMessage(params, 0);
versionMessage.bestHeight = 0;
versionMessage.time = Utils.currentTimeSeconds();
versionMessage.receivingAddr = address;
versionMessage.receivingAddr.setParent(versionMessage);
return versionMessage;
}
}

View File

@ -0,0 +1,150 @@
/*
* 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.btcnodemonitor.btc;
import bisq.core.btc.nodes.BtcNodes;
import bisq.core.btc.nodes.BtcNodesRepository;
import bisq.core.btc.nodes.LocalBitcoinNode;
import bisq.core.btc.nodes.ProxySocketFactory;
import bisq.common.UserThread;
import bisq.common.config.Config;
import bisq.common.util.SingleThreadExecutorUtils;
import org.bitcoinj.core.Context;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.PeerAddress;
import org.bitcoinj.core.PeerGroup;
import org.bitcoinj.net.BlockingClientManager;
import org.bitcoinj.utils.Threading;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import java.time.Duration;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class PeerGroupService {
private final NetworkParameters params;
private final LocalBitcoinNode localBitcoinNode;
private final Context context;
private final PeerConncetionModel peerConncetionModel;
private Set<PeerConnection> peerConnections;
private BlockingClientManager blockingClientManager;
public PeerGroupService(Config config, PeerConncetionModel peerConncetionModel) {
this.peerConncetionModel = peerConncetionModel;
params = Config.baseCurrencyNetworkParameters();
context = new Context(params);
PeerGroup.setIgnoreHttpSeeds(true);
Threading.USER_THREAD = UserThread.getExecutor();
localBitcoinNode = new LocalBitcoinNode(config);
}
public void applySocks5Proxy(Optional<Socks5Proxy> socks5Proxy) {
int connectTimeoutMillis;
int disconnectIntervalSec;
int reconnectIntervalSec;
Set<PeerAddress> peerAddresses;
if (localBitcoinNode.shouldBeUsed()) {
InetSocketAddress address = new InetSocketAddress(InetAddress.getLoopbackAddress().getHostAddress(), params.getPort());
peerAddresses = Set.of(new PeerAddress(address));
connectTimeoutMillis = 1000;
disconnectIntervalSec = 5;
reconnectIntervalSec = 5;
blockingClientManager = new BlockingClientManager();
} else {
BtcNodes btcNodes = new BtcNodes();
List<PeerAddress> peerAddressList = btcNodesToPeerAddress(btcNodes, socks5Proxy);
peerAddresses = new HashSet<>(peerAddressList);
connectTimeoutMillis = socks5Proxy.map(s -> 60_000).orElse(10_000);
disconnectIntervalSec = 2;
reconnectIntervalSec = 120;
if (socks5Proxy.isPresent()) {
InetSocketAddress inetSocketAddress = new InetSocketAddress(socks5Proxy.get().getInetAddress(), socks5Proxy.get().getPort());
Proxy proxy = new Proxy(Proxy.Type.SOCKS, inetSocketAddress);
ProxySocketFactory proxySocketFactory = new ProxySocketFactory(proxy);
blockingClientManager = new BlockingClientManager(proxySocketFactory);
} else {
blockingClientManager = new BlockingClientManager();
}
}
log.info("Using peer addresses {}", peerAddresses);
blockingClientManager.setConnectTimeoutMillis(connectTimeoutMillis);
peerConncetionModel.fill(peerAddresses);
Set<PeerConncetionInfo> peerConncetionInfoSet = new HashSet<>(peerConncetionModel.getMap().values());
peerConnections = peerConncetionInfoSet.stream()
.map(peerConncetionInfo -> new PeerConnection(context,
peerConncetionInfo,
blockingClientManager,
connectTimeoutMillis,
disconnectIntervalSec,
reconnectIntervalSec))
.collect(Collectors.toSet());
}
public CompletableFuture<Void> start() {
return CompletableFuture.runAsync(() -> {
log.info("start");
Context.propagate(context);
blockingClientManager.startAsync();
blockingClientManager.awaitRunning();
}, SingleThreadExecutorUtils.getSingleThreadExecutor("start"));
}
public void connectToAll() {
peerConnections.forEach(PeerConnection::connect);
}
public CompletableFuture<Void> shutdown() {
return CompletableFuture.runAsync(() -> {
log.info("shutdown");
Context.propagate(context);
CountDownLatch latch = new CountDownLatch(peerConnections.size());
peerConnections.forEach(e -> e.shutdown().thenRun(latch::countDown));
try {
latch.await(5, TimeUnit.SECONDS);
blockingClientManager.stopAsync();
blockingClientManager.awaitTerminated(Duration.ofSeconds(2));
} catch (Exception e) {
throw new RuntimeException(e);
}
}, SingleThreadExecutorUtils.getSingleThreadExecutor("shutdown"));
}
private List<PeerAddress> btcNodesToPeerAddress(BtcNodes btcNodes, Optional<Socks5Proxy> proxy) {
List<BtcNodes.BtcNode> nodes = btcNodes.getProvidedBtcNodes();
BtcNodesRepository repository = new BtcNodesRepository(nodes);
return repository.getPeerAddresses(proxy.orElse(null));
}
}

View File

@ -0,0 +1,82 @@
/*
* 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.btcnodemonitor.btc;
import com.google.common.base.Joiner;
import java.util.LinkedList;
import java.util.List;
/**
* Borrowed from VersionMessage and updated to be in sync with
* https://github.com/bitcoin/bitcoin/blob/b1ba1b178f501daa1afdd91f9efec34e5ec1e294/src/protocol.h#L324
*/
public class ServiceBits {
public static final int NODE_NONE = 0;
// NODE_NETWORK means that the node is capable of serving the complete block chain. It is currently
// set by all Bitcoin Core non pruned nodes, and is unset by SPV clients or other light clients.
public static final int NODE_NETWORK = 1 << 0;
// NODE_BLOOM means the node is capable and willing to handle bloom-filtered connections.
public static final int NODE_BLOOM = 1 << 2;
// NODE_WITNESS indicates that a node can be asked for blocks and transactions including
// witness data.
public static final int NODE_WITNESS = 1 << 3;
// NODE_COMPACT_FILTERS means the node will service basic block filter requests.
// See BIP157 and BIP158 for details on how this is implemented.
public static final int NODE_COMPACT_FILTERS = 1 << 6;
// NODE_NETWORK_LIMITED means the same as NODE_NETWORK with the limitation of only
// serving the last 288 (2 day) blocks
// See BIP159 for details on how this is implemented.
public static final int NODE_NETWORK_LIMITED = 1 << 10;
// NODE_P2P_V2 means the node supports BIP324 transport
public static final int NODE_P2P_V2 = 1 << 11;
public static String toString(long services) {
List<String> strings = new LinkedList<>();
if ((services & NODE_NETWORK) == NODE_NETWORK) {
strings.add("NETWORK");
services &= ~NODE_NETWORK;
}
if ((services & NODE_BLOOM) == NODE_BLOOM) {
strings.add("BLOOM");
services &= ~NODE_BLOOM;
}
if ((services & NODE_WITNESS) == NODE_WITNESS) {
strings.add("WITNESS");
services &= ~NODE_WITNESS;
}
if ((services & NODE_COMPACT_FILTERS) == NODE_COMPACT_FILTERS) {
strings.add("COMPACT_FILTERS");
services &= ~NODE_COMPACT_FILTERS;
}
if ((services & NODE_NETWORK_LIMITED) == NODE_NETWORK_LIMITED) {
strings.add("NETWORK_LIMITED");
services &= ~NODE_NETWORK_LIMITED;
}
if ((services & NODE_P2P_V2) == NODE_P2P_V2) {
strings.add("NODE_P2P_V2");
services &= ~NODE_P2P_V2;
}
if (services != 0)
strings.add("Unrecognized service bit: " + Long.toBinaryString(services));
return Joiner.on(", ").join(strings);
}
}

View File

@ -0,0 +1,276 @@
/*
* 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.btcnodemonitor.server;
import bisq.core.btc.nodes.BtcNodes;
import bisq.core.btc.nodes.LocalBitcoinNode;
import bisq.common.config.BaseCurrencyNetwork;
import bisq.common.config.Config;
import bisq.common.util.MathUtils;
import bisq.common.util.Profiler;
import bisq.common.util.SingleThreadExecutorUtils;
import org.bitcoinj.core.VersionMessage;
import org.apache.commons.lang3.time.DurationFormatUtils;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import bisq.btcnodemonitor.btc.PeerConncetionInfo;
import bisq.btcnodemonitor.btc.PeerConncetionModel;
import bisq.btcnodemonitor.btc.ServiceBits;
import spark.Spark;
@Slf4j
public class SimpleHttpServer {
private final static String CLOSE_TAG = "</font><br/>";
private final static String WARNING_ICON = "&#9888; ";
private final static String ALERT_ICON = "&#9760; "; // &#9889; &#9889;
private final Config config;
@Getter
private final List<BtcNodes.BtcNode> providedBtcNodes;
private final Map<String, BtcNodes.BtcNode> btcNodeByAddress;
private final int port;
private final PeerConncetionModel peerConncetionModel;
private final String started;
private String html;
private int requestCounter;
private String networkInfo;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public SimpleHttpServer(Config config, PeerConncetionModel peerConncetionModel) {
this.config = config;
this.peerConncetionModel = peerConncetionModel;
started = new Date().toString();
this.providedBtcNodes = peerConncetionModel.getProvidedBtcNodes();
BaseCurrencyNetwork network = config.baseCurrencyNetwork;
if (config.useTorForBtcMonitor) {
port = network.isMainnet() ? 8000 : 8001;
networkInfo = network.isMainnet() ? "TOR/MAIN_NET" : "TOR/REG_TEST";
btcNodeByAddress = providedBtcNodes.stream()
.filter(e -> e.getOnionAddress() != null)
.collect(Collectors.toMap(BtcNodes.BtcNode::getOnionAddress, e -> e));
} else {
port = network.isMainnet() ? 8080 : 8081;
networkInfo = network.isMainnet() ? "Clearnet/MAIN_NET" : "Clearnet/REG_TEST";
if (new LocalBitcoinNode(config).shouldBeUsed()) {
btcNodeByAddress = new HashMap<>();
btcNodeByAddress.put("127.0.0.1", new BtcNodes.BtcNode("localhost", null, "127.0.0.1",
Config.baseCurrencyNetworkParameters().getPort(), "n/a"));
} else {
if (network.isMainnet()) {
btcNodeByAddress = providedBtcNodes.stream()
.filter(e -> e.getAddress() != null)
.collect(Collectors.toMap(BtcNodes.BtcNode::getAddress, e -> e));
} else {
btcNodeByAddress = new HashMap<>();
}
}
}
html = "Monitor for Bitcoin nodes created for " + networkInfo;
}
public CompletableFuture<Void> start() {
html = "Monitor for Bitcoin nodes starting up for " + networkInfo;
return CompletableFuture.runAsync(() -> {
log.info("Server listen on {}", port);
Spark.port(port);
Spark.get("/", (req, res) -> {
log.info("Incoming request from: {}", req.userAgent());
return html;
});
}, SingleThreadExecutorUtils.getSingleThreadExecutor("SimpleHttpServer.start"));
}
public CompletableFuture<Void> shutdown() {
return CompletableFuture.runAsync(() -> {
log.info("shutDown");
Spark.stop();
log.info("shutDown completed");
});
}
public void onChange() {
StringBuilder sb = new StringBuilder();
sb.append("<result>" +
"<head>" +
"<style type=\"text/css\">" +
" a {" +
" text-decoration:none; color: black;" +
" }" +
" #info { color: #333333; } " +
" #warn { color: #ff7700; } " +
" #error { color: #ff0000; } " +
"table, th, td {border: 1px solid black;}" +
"</style></head>" +
"<body><h3>")
.append("Monitor for Bitcoin nodes using ").append(networkInfo)
.append(", started at: ").append(started).append("<br/>")
.append("System load: ").append(Profiler.getSystemLoad()).append("<br/>")
.append("<br/>").append("<table style=\"width:100%\">")
.append("<tr>")
.append("<th align=\"left\">Node operator</th>")
.append("<th align=\"left\">Connection attempts</th>")
.append("<th align=\"left\">Node info</th>").append("</tr>");
Map<String, PeerConncetionInfo> peersMap = peerConncetionModel.getMap();
btcNodeByAddress.values().stream()
.sorted(Comparator.comparing(BtcNodes.BtcNode::getId))
.forEach(btcNode -> {
sb.append("<tr valign=\"top\">");
String address = btcNode.getAddress();
PeerConncetionInfo peerConncetionInfo = peersMap.get(address);
if (peersMap.containsKey(address)) {
sb.append("<td>").append(getOperatorInfo(btcNode, address)).append("</td>")
.append("<td>").append(getConnectionInfo(peerConncetionInfo)).append("</td>")
.append("<td>").append(getNodeInfo(peerConncetionInfo)).append("</td>");
sb.append("</tr>");
return;
}
address = btcNode.getOnionAddress();
peerConncetionInfo = peersMap.get(address);
if (peersMap.containsKey(address)) {
sb.append("<td>").append(getOperatorInfo(btcNode, address)).append("</td>")
.append("<td>").append(getConnectionInfo(peerConncetionInfo)).append("</td>")
.append("<td>").append(getNodeInfo(peerConncetionInfo)).append("</td>");
} else {
/* sb.append("<td>").append(getOperatorInfo(btcNode, null)).append("</td>")
.append("<td>").append("n/a").append("</td>");*/
}
sb.append("</tr>");
});
sb.append("</table></body></result>");
html = sb.toString();
}
private String getOperatorInfo(BtcNodes.BtcNode btcNode, @Nullable String address) {
StringBuilder sb = new StringBuilder();
sb.append(btcNode.getId()).append("<br/>");
if (address != null) {
sb.append("Address: ").append(address).append("<br/>");
}
return sb.toString();
}
private String getConnectionInfo(PeerConncetionInfo info) {
StringBuilder sb = new StringBuilder();
sb.append("Num connection attempts: ").append(info.getNumConnectionAttempts()).append("<br/>");
double failureRate = info.getFailureRate();
String failureRateString = MathUtils.roundDouble(failureRate * 100, 2) + "%";
if (failureRate >= 0.5) {
failureRateString = asError(failureRateString, failureRateString);
} else if (failureRate >= 0.25) {
failureRateString = asWarn(failureRateString, failureRateString);
} else if (failureRate > 0.) {
failureRateString = asInfo(failureRateString, failureRateString);
}
sb.append("FailureRate (success/failures): ").append(failureRateString)
.append("(").append(info.getNumSuccess()).append(" / ")
.append(info.getNumFailures()).append(")").append("<br/>");
info.getLastExceptionMessage().ifPresent(errorMessage -> {
String allExceptionMessages = info.getAllExceptionMessages();
int indexOfLastError = info.getLastAttemptWithException().map(info::getIndex).orElse(-1);
String msg;
String value = "Last error (connection attempt " + indexOfLastError + "): " + errorMessage;
int tip = info.getConnectionAttempts().size() - 1;
if (indexOfLastError >= tip - 1) {
msg = asError(value, allExceptionMessages);
} else if (indexOfLastError >= tip - 10) {
msg = asWarn(value, allExceptionMessages);
} else {
msg = asInfo(value, allExceptionMessages);
;
}
sb.append(msg).append("<br/>");
});
sb.append("Duration to connect: ").append(MathUtils.roundDouble(info.getLastSuccessfulConnectTime() / 1000d, 2)).append(" sec").append("<br/>");
return sb.toString();
}
private String getNodeInfo(PeerConncetionInfo info) {
if (info.getLastSuccessfulConnected().isEmpty()) {
return "";
}
PeerConncetionInfo.ConnectionAttempt attempt = info.getLastSuccessfulConnected().get();
if (attempt.getVersionMessage().isEmpty()) {
return "";
}
int index = info.getIndex(attempt) + 1;
StringBuilder sb = new StringBuilder();
VersionMessage versionMessage = attempt.getVersionMessage().get();
long peerTime = versionMessage.time * 1000;
long passed = System.currentTimeMillis() - attempt.getConnectionSuccessTs();
String passedString = DurationFormatUtils.formatDurationWords(passed, true, true) + " ago";
// String passedString = MathUtils.roundDouble(passed / 1000d, 2) + " sec. ago";
if (passed > 300_000) {
passedString = asWarn(passedString, passedString);
}
sb.append("Result from connection attempt ").append(index).append(":<br/>");
sb.append("Connected ").append(passedString).append("<br/>");
sb.append("Block height: ").append(versionMessage.bestHeight).append("<br/>");
sb.append("Version: ").append(versionMessage.subVer.replace("/", "")).append(" (").append(versionMessage.clientVersion).append(")").append("<br/>");
String serviceBits = ServiceBits.toString(versionMessage.localServices);
sb.append("Services: ").append(serviceBits)
.append(" (").append(versionMessage.localServices).append(")").append("<br/>");
sb.append("Time: ").append(String.format(Locale.US, "%tF %tT", peerTime, peerTime));
return sb.toString();
}
private static String decorate(String style, String value, String tooltip) {
return "<b><a id=\"" + style + "\" href=\"#\" title=\"" + tooltip + "\">" + value + "</a></b>";
}
private static String asInfo(String value, String tooltip) {
return decorate("info", value, tooltip);
}
private static String asWarn(String value, String tooltip) {
return decorate("warn", value, tooltip);
}
private static String asError(String value, String tooltip) {
return decorate("error", value, tooltip);
}
}

View File

@ -0,0 +1,93 @@
/*
* 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.btcnodemonitor.socksProxy;
import bisq.network.Socks5ProxyProvider;
import bisq.network.p2p.network.NewTor;
import bisq.common.config.Config;
import bisq.common.util.SingleThreadExecutorUtils;
import org.berndpruenster.netlayer.tor.Tor;
import org.berndpruenster.netlayer.tor.TorCtlException;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import java.nio.file.Paths;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkArgument;
@Slf4j
public class ProxySetup {
@Getter
private final File torDir;
private final SocksProxyFactory socksProxyFactory;
private final Config config;
private NewTor torMode;
private Tor tor;
public ProxySetup(Config config) {
this.config = config;
socksProxyFactory = new SocksProxyFactory("127.0.0.1");
Socks5ProxyProvider socks5ProxyProvider = new Socks5ProxyProvider("", "");
socks5ProxyProvider.setSocks5ProxyInternal(socksProxyFactory);
String networkDirName = config.baseCurrencyNetwork.name().toLowerCase();
torDir = Paths.get(config.appDataDir.getPath(), networkDirName, "tor").toFile();
}
public CompletableFuture<Optional<Socks5Proxy>> createSocksProxy() {
log.info("createSocksProxy");
if (!config.useTorForBtcMonitor) {
return CompletableFuture.completedFuture(Optional.empty());
}
checkArgument(tor == null);
return CompletableFuture.supplyAsync(() -> {
torMode = new NewTor(torDir, null, "", ArrayList::new);
try {
// blocking
tor = torMode.getTor();
socksProxyFactory.setTor(tor);
return Optional.of(socksProxyFactory.getSocksProxy());
} catch (IOException | TorCtlException e) {
throw new RuntimeException(e);
}
}, SingleThreadExecutorUtils.getSingleThreadExecutor("ProxySetup.start"));
}
public CompletableFuture<Void> shutdown() {
log.warn("start shutdown");
return CompletableFuture.runAsync(() -> {
if (tor != null) {
tor.shutdown();
}
log.warn("shutdown completed");
})
.orTimeout(2, TimeUnit.SECONDS);
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.btcnodemonitor.socksProxy;
import bisq.network.p2p.network.Socks5ProxyInternalFactory;
import org.berndpruenster.netlayer.tor.Tor;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
@Slf4j
public class SocksProxyFactory implements Socks5ProxyInternalFactory {
private final String torControlHost;
@Setter
@Nullable
private Tor tor;
private Socks5Proxy socksProxy;
public SocksProxyFactory(String torControlHost) {
this.torControlHost = torControlHost;
}
@Override
public Socks5Proxy getSocksProxy() {
if (tor == null) {
return null;
} else {
try {
if (socksProxy == null) {
socksProxy = tor.getProxy(torControlHost, null);
}
return socksProxy;
} catch (Throwable t) {
log.error("Error at getSocksProxy", t);
return null;
}
}
}
}

View File

@ -0,0 +1,17 @@
<?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{40}: %msg %xEx%n)</pattern>
</encoder>
</appender>
<root level="TRACE">
<appender-ref ref="CONSOLE_APPENDER"/>
</root>
<logger name="org.bitcoinj" level="INFO"/>
<logger name="spark.embeddedserver" level="WARN"/>
<logger name="org.eclipse.jetty.server" level="WARN"/>
<logger name="org.berndpruenster.netlayer.tor.Tor" level="WARN"/>
</configuration>

View File

@ -138,6 +138,7 @@ public class Config {
public static final String BM_ORACLE_NODE_PUB_KEY = "bmOracleNodePubKey";
public static final String BM_ORACLE_NODE_PRIV_KEY = "bmOracleNodePrivKey";
public static final String SEED_NODE_REPORTING_SERVER_URL = "seedNodeReportingServerUrl";
public static final String USE_TOR_FOR_BTC_MONITOR = "useTorForBtcMonitor";
// Default values for certain options
public static final int UNSPECIFIED_PORT = -1;
@ -237,6 +238,7 @@ public class Config {
public final String bmOracleNodePubKey;
public final String bmOracleNodePrivKey;
public final String seedNodeReportingServerUrl;
public final boolean useTorForBtcMonitor;
// Properties derived from options but not exposed as options themselves
public final File torDir;
@ -738,6 +740,12 @@ public class Config {
.ofType(String.class)
.defaultsTo("");
ArgumentAcceptingOptionSpec<Boolean> useTorForBtcMonitorOpt =
parser.accepts(USE_TOR_FOR_BTC_MONITOR, "If set to true BitcoinJ is routed over tor (socks 5 proxy) for Bitcoin monitor.")
.withRequiredArg()
.ofType(Boolean.class)
.defaultsTo(true);
try {
CompositeOptionSet options = new CompositeOptionSet();
@ -864,6 +872,7 @@ public class Config {
this.bmOracleNodePubKey = options.valueOf(bmOracleNodePubKey);
this.bmOracleNodePrivKey = options.valueOf(bmOracleNodePrivKey);
this.seedNodeReportingServerUrl = options.valueOf(seedNodeReportingServerUrlOpt);
this.useTorForBtcMonitor = options.valueOf(useTorForBtcMonitorOpt);
} catch (OptionException ex) {
throw new ConfigException("problem parsing option '%s': %s",
ex.options().get(0),

View File

@ -30,12 +30,16 @@ public class Profiler {
}
public static void printSystemLoad() {
log.info(getSystemLoad());
}
public static String getSystemLoad() {
Runtime runtime = Runtime.getRuntime();
long free = runtime.freeMemory();
long total = runtime.totalMemory();
long used = total - free;
log.info("Total memory: {}; Used memory: {}; Free memory: {}; Max memory: {}; No. of threads: {}",
return String.format("Total memory: %s; Used memory: %s; Free memory: %s; Max memory: %s; No. of threads: %s",
Utilities.readableFileSize(total),
Utilities.readableFileSize(used),
Utilities.readableFileSize(free),

View File

@ -48,20 +48,37 @@ public class BtcNodes {
return useProvidedBtcNodes() ?
Arrays.asList(
// emzy
new BtcNode("btcnode3.emzy.de", "emzybtc5bnpb2o6gh54oquiox54o4r7yn4a2wiiwzrjonlouaibm2zid.onion", "136.243.53.40", BtcNode.DEFAULT_PORT, "@emzy"),
new BtcNode("btcnode1.emzy.de", "emzybtc3ewh7zihpkdvuwlgxrhzcxy2p5fvjggp7ngjbxcytxvt4rjid.onion", "167.86.90.239", BtcNode.DEFAULT_PORT, "@emzy"),
// emzy unstable
new BtcNode("btcnode2.emzy.de", "emzybtc25oddoa2prol2znpz2axnrg6k77xwgirmhv7igoiucddsxiad.onion", "62.171.129.32", BtcNode.DEFAULT_PORT, "@emzy (unstable)"),
new BtcNode("btcnode4.emzy.de", "emzybtc454ewbviqnmgtgx3rgublsgkk23r4onbhidcv36wremue4kqd.onion", "135.181.215.237", BtcNode.DEFAULT_PORT, "@emzy (very unstable)"),
// mrosseel
new BtcNode("btc.vante.me", "bsqbtctulf2g4jtjsdfgl2ed7qs6zz5wqx27qnyiik7laockryvszqqd.onion", "94.23.21.80", BtcNode.DEFAULT_PORT, "@miker"),
// mrosseel unstable
new BtcNode("btc.vante.me", "bsqbtctulf2g4jtjsdfgl2ed7qs6zz5wqx27qnyiik7laockryvszqqd.onion", "94.23.21.80", BtcNode.DEFAULT_PORT, "@miker (unstable)"),
// sqrrm
new BtcNode("btc1.sqrrm.net", "cwi3ekrwhig47dhhzfenr5hbvckj7fzaojygvazi2lucsenwbzwoyiqd.onion", "185.25.48.184", BtcNode.DEFAULT_PORT, "@sqrrm"),
new BtcNode("btc2.sqrrm.net", "upvthy74hgvgbqi6w3zd2mlchoi5tvvw7b5hpmmhcddd5fnnwrixneid.onion", "81.171.22.143", BtcNode.DEFAULT_PORT, "@sqrrm"),
// sqrrm unstable
new BtcNode("btc2.sqrrm.net", "upvthy74hgvgbqi6w3zd2mlchoi5tvvw7b5hpmmhcddd5fnnwrixneid.onion", "81.171.22.143", BtcNode.DEFAULT_PORT, "@sqrrm (unstable)"),
// Devin Bileck
new BtcNode("btc1.bisq.services", "devinbtctu7uctl7hly2juu3thbgeivfnvw3ckj3phy6nyvpnx66yeyd.onion", "172.105.21.216", BtcNode.DEFAULT_PORT, "@devinbileck"),
new BtcNode(null, "devinbtcyk643iruzfpaxw3on2jket7rbjmwygm42dmdyub3ietrbmid.onion", null, BtcNode.DEFAULT_PORT, "@devinbileck"),
new BtcNode(null, "devinbtcmwkuitvxl3tfi5of4zau46ymeannkjv6fpnylkgf3q5fa3id.onion", null, BtcNode.DEFAULT_PORT, "@devinbileck"),
// wiz
new BtcNode("node210.fmt.wiz.biz", "rfqmn3qe36uaptkxhdvi74p4hyrzhir6vhmzb2hqryxodig4gue2zbyd.onion", "103.99.170.210", BtcNode.DEFAULT_PORT, "@wiz"),
// wiz unstable
new BtcNode("node220.fmt.wiz.biz", "azbpsh4arqlm6442wfimy7qr65bmha2zhgjg7wbaji6vvaug53hur2qd.onion", "103.99.170.220", BtcNode.DEFAULT_PORT, "@wiz (very unstable)"),
// jester4042
new BtcNode(null, "nhpftqp3kmcnksw2ev6tkwq47jhy37movbjimnd577jcrtwmadtirrqd.onion", null, BtcNode.DEFAULT_PORT, "@jester4042"),
// node_op_324
new BtcNode(null, "qs535l32ne43rxr5iqexbhu4r6zifrfez653pm7j3rpi7c7omaz7xcqd.onion", null, BtcNode.DEFAULT_PORT, "@node_op_324"),
// btcNodl
new BtcNode(null, "2oalsctcn76axnrnaqjddiiu5qhrc7hv3raik2lyfxb7eoktk4vw6sad.onion", null, BtcNode.DEFAULT_PORT, "@btcNodl"),
// runbtc
new BtcNode(null, "runbtcnd22qxdwlmhzsrw6zyfmkivuy5nuqbhasaztekildcxc7lseyd.onion", null, BtcNode.DEFAULT_PORT, "@runbtc")
) :
@ -161,5 +178,11 @@ public class BtcNodes {
", port='" + port + '\'' +
", operator='" + operator;
}
public String getId() {
String address = this.address == null ? "" : this.address + ", ";
String onionAddress = this.onionAddress == null ? "" : this.onionAddress;
return operator + ": [" + address + onionAddress + "]";
}
}
}

View File

@ -42,6 +42,10 @@ public class BtcNodesRepository {
this.nodes = nodes;
}
public List<PeerAddress> getPeerAddresses(@Nullable Socks5Proxy proxy) {
return getPeerAddresses(proxy, false);
}
public List<PeerAddress> getPeerAddresses(@Nullable Socks5Proxy proxy, boolean isUseClearNodesWithProxies) {
List<PeerAddress> result;
// We connect to onion nodes only in case we use Tor for BitcoinJ (default) to avoid privacy leaks at

View File

@ -17,12 +17,12 @@
package bisq.network;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import bisq.network.p2p.network.NetworkNode;
import bisq.network.p2p.network.Socks5ProxyInternalFactory;
import bisq.common.config.Config;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import com.google.inject.Inject;
import javax.inject.Named;
@ -49,7 +49,7 @@ public class Socks5ProxyProvider {
private static final Logger log = LoggerFactory.getLogger(Socks5ProxyProvider.class);
@Nullable
private NetworkNode socks5ProxyInternalFactory;
private Socks5ProxyInternalFactory socks5ProxyInternalFactory;
// proxy used for btc network
@Nullable
@ -91,7 +91,7 @@ public class Socks5ProxyProvider {
return socks5ProxyInternalFactory.getSocksProxy();
}
public void setSocks5ProxyInternal(@Nullable NetworkNode bisqSocks5ProxyFactory) {
public void setSocks5ProxyInternal(@Nullable Socks5ProxyInternalFactory bisqSocks5ProxyFactory) {
this.socks5ProxyInternalFactory = bisqSocks5ProxyFactory;
}

View File

@ -64,7 +64,7 @@ import org.jetbrains.annotations.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
// Run in UserThread
public abstract class NetworkNode implements MessageListener {
public abstract class NetworkNode implements MessageListener, Socks5ProxyInternalFactory {
private static final Logger log = LoggerFactory.getLogger(NetworkNode.class);
private static final int CREATE_SOCKET_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(120);
@ -277,6 +277,7 @@ public abstract class NetworkNode implements MessageListener {
}
}
@Override
@Nullable
public Socks5Proxy getSocksProxy() {
return null;

View File

@ -0,0 +1,24 @@
/*
* 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.network.p2p.network;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
public interface Socks5ProxyInternalFactory {
Socks5Proxy getSocksProxy();
}

View File

@ -104,6 +104,7 @@ public class TorNetworkNode extends NetworkNode {
return new TorSocket(peerNodeAddress.getHostName(), peerNodeAddress.getPort(), torControlHost, null);
}
@Override
public Socks5Proxy getSocksProxy() {
try {
String stream = null;

View File

@ -21,6 +21,7 @@ toolchainManagement {
include 'proto'
include 'assets'
include 'btcnodemonitor'
include 'common'
include 'p2p'
include 'core'