From cda43dc2f4e11c1020ba7d4df1b41994fc0d445b Mon Sep 17 00:00:00 2001 From: itorod Date: Sun, 22 Sep 2024 13:10:49 +0100 Subject: [PATCH 1/2] added PeerMonitor class that uses javafx --- examples/build.gradle | 6 + .../org/bitcoinj/examples/PeerMonitorFx.java | 248 ++++++++++++++++++ .../bitcoinj/examples/PeerMonitorMain.java | 7 + 3 files changed, 261 insertions(+) create mode 100644 examples/src/main/java/org/bitcoinj/examples/PeerMonitorFx.java create mode 100644 examples/src/main/java/org/bitcoinj/examples/PeerMonitorMain.java diff --git a/examples/build.gradle b/examples/build.gradle index 6f444972f..a10a33c17 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -1,5 +1,11 @@ plugins { id 'java' + id 'org.openjfx.javafxplugin' version '0.0.14' +} + +javafx { + version = '17' + modules = [ 'javafx.controls', 'javafx.fxml' ] } dependencies { diff --git a/examples/src/main/java/org/bitcoinj/examples/PeerMonitorFx.java b/examples/src/main/java/org/bitcoinj/examples/PeerMonitorFx.java new file mode 100644 index 000000000..34a1e85f7 --- /dev/null +++ b/examples/src/main/java/org/bitcoinj/examples/PeerMonitorFx.java @@ -0,0 +1,248 @@ +package org.bitcoinj.examples; + +import javafx.application.Application; +import javafx.application.Platform; +import javafx.beans.property.SimpleStringProperty; +import javafx.concurrent.Task; +import javafx.geometry.Insets; +import javafx.scene.Scene; +import javafx.scene.control.*; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.stage.Stage; +import org.bitcoinj.base.BitcoinNetwork; +import org.bitcoinj.base.Coin; +import org.bitcoinj.base.Network; +import org.bitcoinj.core.AddressMessage; +import org.bitcoinj.core.Peer; +import org.bitcoinj.core.PeerAddress; +import org.bitcoinj.core.PeerGroup; +import org.bitcoinj.net.discovery.DnsDiscovery; +import org.bitcoinj.utils.BriefLogFormatter; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +public class PeerMonitorFx extends Application { + + private PeerGroup peerGroup; + private final Executor reverseDnsThreadPool = Executors.newCachedThreadPool(); + private final ConcurrentHashMap reverseDnsLookups = new ConcurrentHashMap<>(); + private final ConcurrentHashMap addressMessages = new ConcurrentHashMap<>(); + private TableView peerTable; + private Spinner numPeersSpinner; + + public static void main(String[] args) { + BriefLogFormatter.init(); + launch(args); + } + + @Override + public void start(Stage primaryStage) { + setupNetwork(); + + primaryStage.setTitle("BitcoinJ Peer Monitor"); + + BorderPane root = new BorderPane(); + + // Top controls: Spinner for number of peers + HBox topControls = new HBox(); + topControls.setSpacing(10); + topControls.setPadding(new Insets(15, 12, 15, 12)); + + Label instructions = new Label("Number of peers to connect to:"); + numPeersSpinner = new Spinner<>(0, 100, 4); + numPeersSpinner.valueProperty().addListener((obs, oldValue, newValue) -> peerGroup.setMaxConnections(newValue)); + + topControls.getChildren().addAll(instructions, numPeersSpinner); + + // Center: Table view for peers + peerTable = new TableView<>(); + setupPeerTable(); + root.setTop(topControls); + root.setCenter(peerTable); + + // Scene and stage setup + Scene scene = new Scene(root, 1280, 768); + primaryStage.setScene(scene); + primaryStage.setOnCloseRequest(event -> { + System.out.println("Shutting down..."); + peerGroup.stop(); + System.out.println("Shutdown complete."); + Platform.exit(); + }); + primaryStage.show(); + + peerGroup.startAsync(); + startPeerTableUpdater(); + } + + private void setupNetwork() { + Network network = BitcoinNetwork.MAINNET; + peerGroup = new PeerGroup(network, null); + peerGroup.setUserAgent("PeerMonitorFX", "1.0"); + peerGroup.setMaxConnections(4); + peerGroup.addPeerDiscovery(new DnsDiscovery(network)); + peerGroup.addConnectedEventListener((peer, peerCount) -> { + Platform.runLater(this::updatePeerTable); + lookupReverseDNS(peer); + getAddr(peer); + }); + peerGroup.addDisconnectedEventListener((peer, peerCount) -> { + Platform.runLater(this::updatePeerTable); + reverseDnsLookups.remove(peer); + addressMessages.remove(peer); + }); + } + + private void lookupReverseDNS(Peer peer) { + getHostName(peer.getAddress()).thenAccept(reverseDns -> { + reverseDnsLookups.put(peer, reverseDns); + Platform.runLater(this::updatePeerTable); + }); + } + + private void getAddr(Peer peer) { + peer.getAddr().orTimeout(15, java.util.concurrent.TimeUnit.SECONDS).whenComplete((addressMessage, e) -> { + if (addressMessage != null) { + addressMessages.put(peer, addressMessage); + Platform.runLater(this::updatePeerTable); + } else { + e.printStackTrace(); + } + }); + } + + private CompletableFuture getHostName(PeerAddress peerAddress) { + if (peerAddress.getAddr() != null) { + return CompletableFuture.supplyAsync(peerAddress.getAddr()::getCanonicalHostName, reverseDnsThreadPool); + } else if (peerAddress.getHostname() != null) { + return CompletableFuture.completedFuture(peerAddress.getHostname()); + } else { + return CompletableFuture.completedFuture("-unavailable-"); + } + } + + private void setupPeerTable() { + TableColumn addressColumn = new TableColumn<>("Address"); + addressColumn.setCellValueFactory(new PropertyValueFactory<>("address")); + + TableColumn userAgentColumn = new TableColumn<>("User Agent"); + userAgentColumn.setCellValueFactory(new PropertyValueFactory<>("userAgent")); + + TableColumn chainHeightColumn = new TableColumn<>("Chain height"); + chainHeightColumn.setCellValueFactory(new PropertyValueFactory<>("chainHeight")); + + TableColumn protocolVersionColumn = new TableColumn<>("Protocol Version"); + protocolVersionColumn.setCellValueFactory(new PropertyValueFactory<>("protocolVersion")); + + TableColumn feeFilterColumn = new TableColumn<>("Fee Filter"); + feeFilterColumn.setCellValueFactory(new PropertyValueFactory<>("feeFilter")); + + TableColumn pingTimeColumn = new TableColumn<>("Ping Time"); + pingTimeColumn.setCellValueFactory(new PropertyValueFactory<>("pingTime")); + + TableColumn lastPingTimeColumn = new TableColumn<>("Last Ping Time"); + lastPingTimeColumn.setCellValueFactory(new PropertyValueFactory<>("lastPingTime")); + + TableColumn addressesColumn = new TableColumn<>("Peer Addresses"); + addressesColumn.setCellValueFactory(new PropertyValueFactory<>("addresses")); + + peerTable.getColumns().addAll(addressColumn, userAgentColumn, chainHeightColumn, protocolVersionColumn, feeFilterColumn, pingTimeColumn, lastPingTimeColumn, addressesColumn); + } + + private void updatePeerTable() { + List connectedPeers = peerGroup.getConnectedPeers(); + List peerDataList = new ArrayList<>(); + for (Peer peer : connectedPeers) { + String address = reverseDnsLookups.getOrDefault(peer, peer.getAddress().toString()); + String userAgent = peer.getPeerVersionMessage().subVer; + long chainHeight = peer.getBestHeight(); + String protocolVersion = Integer.toString(peer.getPeerVersionMessage().clientVersion); + Coin feeFilter = peer.getFeeFilter(); + String feeFilterStr = feeFilter != null ? feeFilter.toFriendlyString() : ""; + long pingTime = peer.pingInterval().map(Duration::toMillis).orElse(0L); + long lastPingTime = peer.lastPingInterval().map(Duration::toMillis).orElse(0L); + String addresses = addressMessages.containsKey(peer) ? addressMessages.get(peer).toString() : "-unavailable-"; + peerDataList.add(new PeerData(address, userAgent, chainHeight, protocolVersion, feeFilterStr, pingTime, lastPingTime, addresses)); + } + peerTable.getItems().setAll(peerDataList); + } + + private void startPeerTableUpdater() { + Task task = new Task<>() { + @Override + protected Void call() { + while (true) { + Platform.runLater(PeerMonitorFx.this::updatePeerTable); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + }; + new Thread(task).start(); + } + + public static class PeerData { + private final SimpleStringProperty address; + private final SimpleStringProperty userAgent; + private final SimpleStringProperty chainHeight; + private final SimpleStringProperty protocolVersion; + private final SimpleStringProperty feeFilter; + private final SimpleStringProperty pingTime; + private final SimpleStringProperty lastPingTime; + private final SimpleStringProperty addresses; + + public PeerData(String address, String userAgent, long chainHeight, String protocolVersion, String feeFilter, long pingTime, long lastPingTime, String addresses) { + this.address = new SimpleStringProperty(address); + this.userAgent = new SimpleStringProperty(userAgent); + this.chainHeight = new SimpleStringProperty(Long.toString(chainHeight)); + this.protocolVersion = new SimpleStringProperty(protocolVersion); + this.feeFilter = new SimpleStringProperty(feeFilter); + this.pingTime = new SimpleStringProperty(Long.toString(pingTime)); + this.lastPingTime = new SimpleStringProperty(Long.toString(lastPingTime)); + this.addresses = new SimpleStringProperty(addresses); + } + + public String getAddress() { + return address.get(); + } + + public String getUserAgent() { + return userAgent.get(); + } + + public String getChainHeight() { + return chainHeight.get(); + } + + public String getProtocolVersion() { + return protocolVersion.get(); + } + + public String getFeeFilter() { + return feeFilter.get(); + } + + public String getPingTime() { + return pingTime.get(); + } + + public String getLastPingTime() { + return lastPingTime.get(); + } + + public String getAddresses() { + return addresses.get(); + } + } +} diff --git a/examples/src/main/java/org/bitcoinj/examples/PeerMonitorMain.java b/examples/src/main/java/org/bitcoinj/examples/PeerMonitorMain.java new file mode 100644 index 000000000..7bee45077 --- /dev/null +++ b/examples/src/main/java/org/bitcoinj/examples/PeerMonitorMain.java @@ -0,0 +1,7 @@ +package org.bitcoinj.examples; + +public class PeerMonitorMain { + public static void main(String[] args){ + PeerMonitorFx.main(args); + } +} From d9d7a080e527201d644a1007ad7a42046c44425b Mon Sep 17 00:00:00 2001 From: itorouk Date: Sun, 8 Dec 2024 20:47:05 +0300 Subject: [PATCH 2/2] reduced size of scene --- .../src/main/java/org/bitcoinj/examples/PeerMonitorFx.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/src/main/java/org/bitcoinj/examples/PeerMonitorFx.java b/examples/src/main/java/org/bitcoinj/examples/PeerMonitorFx.java index 34a1e85f7..272c4793c 100644 --- a/examples/src/main/java/org/bitcoinj/examples/PeerMonitorFx.java +++ b/examples/src/main/java/org/bitcoinj/examples/PeerMonitorFx.java @@ -64,12 +64,14 @@ public class PeerMonitorFx extends Application { // Center: Table view for peers peerTable = new TableView<>(); + peerTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + setupPeerTable(); root.setTop(topControls); root.setCenter(peerTable); // Scene and stage setup - Scene scene = new Scene(root, 1280, 768); + Scene scene = new Scene(root, 800, 600); primaryStage.setScene(scene); primaryStage.setOnCloseRequest(event -> { System.out.println("Shutting down...");