mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-01-30 23:02:15 +00:00
Introduce a PeerGroup method for calculating the most common chain height.
Use it to make peer selection a little smarter. Render pending peers and peers that are reporting un-common chain heights in PeerMonitor.
This commit is contained in:
parent
2a55c58460
commit
07011be796
@ -69,9 +69,10 @@ public class PeerGroup extends AbstractIdleService {
|
||||
// These lists are all thread-safe so do not have to be accessed under the PeerGroup lock.
|
||||
// Addresses to try to connect to, excluding active peers.
|
||||
private List<PeerAddress> inactives;
|
||||
// Currently active peers. This is an ordered list rather than a set to make unit tests predictable.
|
||||
// Currently active peers. This is an ordered list rather than a set to make unit tests predictable. This is a
|
||||
// synchronized list. Locking order is: PeerGroup < Peer < peers. Same for pendingPeers.
|
||||
private List<Peer> peers;
|
||||
// Currently connecting peers
|
||||
// Currently connecting peers.
|
||||
private List<Peer> pendingPeers;
|
||||
private Map<Peer, ChannelFuture> channelFutures;
|
||||
|
||||
@ -386,9 +387,16 @@ public class PeerGroup extends AbstractIdleService {
|
||||
*/
|
||||
public List<Peer> getConnectedPeers() {
|
||||
synchronized (peers) {
|
||||
ArrayList<Peer> result = new ArrayList<Peer>(peers.size());
|
||||
result.addAll(peers);
|
||||
return result;
|
||||
return new ArrayList<Peer>(peers);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list containing Peers that did not complete connection yet.
|
||||
*/
|
||||
public List<Peer> getPendingPeers() {
|
||||
synchronized (pendingPeers) {
|
||||
return new ArrayList<Peer>(pendingPeers);
|
||||
}
|
||||
}
|
||||
|
||||
@ -638,12 +646,11 @@ public class PeerGroup extends AbstractIdleService {
|
||||
// Link the peer to the memory pool so broadcast transactions have their confidence levels updated.
|
||||
peer.setMemoryPool(memoryPool);
|
||||
// If we want to download the chain, and we aren't currently doing so, do so now.
|
||||
// TODO: Change this so we automatically switch the download peer based on ping times.
|
||||
if (downloadListener != null && downloadPeer == null) {
|
||||
if (downloadListener != null && downloadPeer == null && chain != null) {
|
||||
log.info(" starting block chain download");
|
||||
startBlockChainDownloadFromPeer(peer);
|
||||
} else if (downloadPeer == null) {
|
||||
setDownloadPeer(peer);
|
||||
setDownloadPeer(selectDownloadPeer(peers));
|
||||
} else {
|
||||
peer.setDownloadData(false);
|
||||
}
|
||||
@ -741,8 +748,12 @@ public class PeerGroup extends AbstractIdleService {
|
||||
}
|
||||
|
||||
private synchronized void setDownloadPeer(Peer peer) {
|
||||
if (chain == null)
|
||||
if (chain == null) {
|
||||
// PeerGroup creator did not want us to download any data. We still track the download peer for
|
||||
// informational purposes.
|
||||
downloadPeer = peer;
|
||||
return;
|
||||
}
|
||||
if (downloadPeer != null) {
|
||||
log.info("Unsetting download peer: {}", downloadPeer);
|
||||
downloadPeer.setDownloadData(false);
|
||||
@ -805,12 +816,13 @@ public class PeerGroup extends AbstractIdleService {
|
||||
log.info("Download peer died. Picking a new one.");
|
||||
setDownloadPeer(null);
|
||||
// Pick a new one and possibly tell it to download the chain.
|
||||
// TODO: Fix lock inversion here.
|
||||
synchronized (peers) {
|
||||
if (!peers.isEmpty()) {
|
||||
Peer next = peers.get(0);
|
||||
setDownloadPeer(next);
|
||||
Peer newDownloadPeer = selectDownloadPeer(peers);
|
||||
if (newDownloadPeer != null) {
|
||||
setDownloadPeer(newDownloadPeer);
|
||||
if (downloadListener != null) {
|
||||
startBlockChainDownloadFromPeer(next);
|
||||
startBlockChainDownloadFromPeer(newDownloadPeer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -932,6 +944,7 @@ public class PeerGroup extends AbstractIdleService {
|
||||
public void run() {
|
||||
// This can be called immediately if we already have enough peers. Otherwise it'll be called from a
|
||||
// peer thread.
|
||||
// TODO: Fix the race that exists here.
|
||||
final Peer somePeer = peers.get(0);
|
||||
log.info("broadcastTransaction: Enough peers, adding {} to the memory pool and sending to {}",
|
||||
tx.getHashAsString(), somePeer);
|
||||
@ -1034,6 +1047,67 @@ public class PeerGroup extends AbstractIdleService {
|
||||
this.pingIntervalMsec = pingIntervalMsec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns our peers most commonly reported chain height. If multiple heights are tied, the highest is returned.
|
||||
* If no peers are connected, returns zero.
|
||||
*/
|
||||
public int getMostCommonChainHeight() {
|
||||
// Copy the peers list so we can calculate on it without violating lock ordering: Peer < peers
|
||||
ArrayList<Peer> peers;
|
||||
synchronized (this.peers) {
|
||||
peers = new ArrayList<Peer>(this.peers);
|
||||
}
|
||||
if (peers.isEmpty())
|
||||
return 0;
|
||||
int s = peers.size();
|
||||
int[] heights = new int[s];
|
||||
int[] counts = new int[s];
|
||||
int maxCount = 0;
|
||||
// Calculate the frequencies of each reported height.
|
||||
for (Peer peer : peers) {
|
||||
int h = (int) peer.getBestHeight();
|
||||
// Find the index of the peers height in the heights array.
|
||||
for (int cursor = 0; cursor < s; cursor++) {
|
||||
if (heights[cursor] == h) {
|
||||
maxCount = Math.max(++counts[cursor], maxCount);
|
||||
break;
|
||||
} else if (heights[cursor] == 0) {
|
||||
// A new height we didn't see before.
|
||||
Preconditions.checkState(counts[cursor] == 0);
|
||||
heights[cursor] = h;
|
||||
counts[cursor] = 1;
|
||||
maxCount = Math.max(maxCount, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Find the heights that have the highest frequencies.
|
||||
int[] freqHeights = new int[s];
|
||||
int cursor = 0;
|
||||
for (int i = 0; i < s; i++) {
|
||||
if (counts[i] == maxCount) {
|
||||
freqHeights[cursor++] = heights[i];
|
||||
}
|
||||
}
|
||||
// Return the highest of the most common heights.
|
||||
Arrays.sort(freqHeights);
|
||||
return freqHeights[s - 1];
|
||||
}
|
||||
|
||||
/** Given a list of Peers, return a Peer to be used as the download peer. */
|
||||
protected Peer selectDownloadPeer(List<Peer> peers) {
|
||||
synchronized (peers) {
|
||||
if (peers.isEmpty())
|
||||
return null;
|
||||
// Make sure we don't select a peer that is behind/synchronizing itself.
|
||||
int mostCommonChainHeight = getMostCommonChainHeight();
|
||||
for (Peer peer : peers) {
|
||||
if (peer.getBestHeight() == mostCommonChainHeight) return peer;
|
||||
}
|
||||
throw new IllegalStateException("Unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
private static class PeerGroupThreadFactory implements ThreadFactory {
|
||||
static final AtomicInteger poolNumber = new AtomicInteger(1);
|
||||
final ThreadGroup group;
|
||||
|
@ -20,6 +20,7 @@ import com.google.bitcoin.discovery.PeerDiscovery;
|
||||
import com.google.bitcoin.discovery.PeerDiscoveryException;
|
||||
import org.jboss.netty.bootstrap.ClientBootstrap;
|
||||
import org.jboss.netty.channel.*;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
@ -71,6 +72,11 @@ public class PeerGroupTest extends TestWithNetworkConnections {
|
||||
peerGroup.setPingIntervalMsec(0); // Disable the pings as they just get in the way of most tests.
|
||||
}
|
||||
|
||||
@After
|
||||
public void shutDown() throws Exception {
|
||||
peerGroup.stopAndWait();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listener() throws Exception {
|
||||
AbstractPeerEventListener listener = new AbstractPeerEventListener() {
|
||||
@ -395,4 +401,19 @@ public class PeerGroupTest extends TestWithNetworkConnections {
|
||||
inbound(p1, new Pong(ping.getNonce()));
|
||||
assertTrue(peerGroup.getConnectedPeers().get(0).getLastPingTime() < Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void commonChainHeights() throws Exception {
|
||||
peerGroup.startAndWait();
|
||||
VersionMessage versionMessage2 = new VersionMessage(params, 2);
|
||||
VersionMessage versionMessage3 = new VersionMessage(params, 3);
|
||||
connectPeer(1, versionMessage2);
|
||||
assertEquals(2, peerGroup.getMostCommonChainHeight());
|
||||
connectPeer(2, versionMessage2);
|
||||
assertEquals(2, peerGroup.getMostCommonChainHeight());
|
||||
connectPeer(3, versionMessage3);
|
||||
assertEquals(2, peerGroup.getMostCommonChainHeight());
|
||||
connectPeer(4, versionMessage3);
|
||||
assertEquals(3, peerGroup.getMostCommonChainHeight());
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import javax.swing.table.TableCellRenderer;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
@ -41,6 +42,7 @@ public class PeerMonitor {
|
||||
private NetworkParameters params;
|
||||
private PeerGroup peerGroup;
|
||||
private PeerTableModel peerTableModel;
|
||||
private PeerTableRenderer peerTableRenderer;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
BriefLogFormatter.init();
|
||||
@ -76,7 +78,7 @@ public class PeerMonitor {
|
||||
// Tell the Swing UI thread to redraw the peers table.
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
peerTableModel.fireTableDataChanged();
|
||||
peerTableModel.updateFromPeerGroup();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -110,6 +112,11 @@ public class PeerMonitor {
|
||||
peerTableModel = new PeerTableModel();
|
||||
JTable peerTable = new JTable(peerTableModel);
|
||||
peerTable.setAutoCreateRowSorter(true);
|
||||
peerTableRenderer = new PeerTableRenderer(peerTableModel);
|
||||
peerTable.setDefaultRenderer(String.class, peerTableRenderer);
|
||||
peerTable.setDefaultRenderer(Integer.class, peerTableRenderer);
|
||||
peerTable.setDefaultRenderer(Long.class, peerTableRenderer);
|
||||
|
||||
JScrollPane scrollPane = new JScrollPane(peerTable);
|
||||
window.getContentPane().add(scrollPane, BorderLayout.CENTER);
|
||||
window.pack();
|
||||
@ -119,21 +126,30 @@ public class PeerMonitor {
|
||||
// Refresh the UI every half second to get the latest ping times. The event handler runs in the UI thread.
|
||||
new Timer(1000, new ActionListener() {
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
peerTableModel.fireTableDataChanged();
|
||||
peerTableModel.updateFromPeerGroup();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private class PeerTableModel extends AbstractTableModel {
|
||||
private final int IP_ADDRESS = 0;
|
||||
private final int PROTOCOL_VERSION = 1;
|
||||
private final int USER_AGENT = 2;
|
||||
private final int CHAIN_HEIGHT = 3;
|
||||
private final int PING_TIME = 4;
|
||||
private final int LAST_PING_TIME = 5;
|
||||
public static final int IP_ADDRESS = 0;
|
||||
public static final int PROTOCOL_VERSION = 1;
|
||||
public static final int USER_AGENT = 2;
|
||||
public static final int CHAIN_HEIGHT = 3;
|
||||
public static final int PING_TIME = 4;
|
||||
public static final int LAST_PING_TIME = 5;
|
||||
|
||||
public List<Peer> connectedPeers;
|
||||
public List<Peer> pendingPeers;
|
||||
|
||||
public void updateFromPeerGroup() {
|
||||
connectedPeers = peerGroup.getConnectedPeers();
|
||||
pendingPeers = peerGroup.getPendingPeers();
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
public int getRowCount() {
|
||||
return peerGroup.numConnectedPeers();
|
||||
return peerGroup.numConnectedPeers() + peerGroup.getPendingPeers().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -167,7 +183,25 @@ public class PeerMonitor {
|
||||
}
|
||||
|
||||
public Object getValueAt(int row, int col) {
|
||||
// This is racy. A peer can be moving from pending to connected between these two lines.
|
||||
List<Peer> peers = peerGroup.getConnectedPeers();
|
||||
List<Peer> pendingPeers = peerGroup.getPendingPeers();
|
||||
if (row >= peers.size()) {
|
||||
// Peer that isn't connected yet.
|
||||
Peer peer = pendingPeers.get(row - peers.size());
|
||||
switch (col) {
|
||||
case IP_ADDRESS:
|
||||
return peer.getAddress().getAddr().getHostAddress();
|
||||
case PROTOCOL_VERSION:
|
||||
return 0;
|
||||
case CHAIN_HEIGHT:
|
||||
case PING_TIME:
|
||||
case LAST_PING_TIME:
|
||||
return 0L;
|
||||
default:
|
||||
return "(pending)";
|
||||
}
|
||||
}
|
||||
Peer peer = peers.get(row);
|
||||
switch (col) {
|
||||
case IP_ADDRESS:
|
||||
@ -190,4 +224,44 @@ public class PeerMonitor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class PeerTableRenderer extends JLabel implements TableCellRenderer {
|
||||
private final PeerTableModel model;
|
||||
private final Font normal, bold;
|
||||
|
||||
public PeerTableRenderer(PeerTableModel model) {
|
||||
super();
|
||||
this.model = model;
|
||||
this.normal = new Font("Sans Serif", Font.PLAIN, 12);
|
||||
this.bold = new Font("Sans Serif", Font.BOLD, 12);
|
||||
}
|
||||
|
||||
public Component getTableCellRendererComponent(JTable table, Object contents,
|
||||
boolean selected, boolean hasFocus, int row, int column) {
|
||||
setText(contents.toString());
|
||||
if (model.connectedPeers == null || model.pendingPeers == null) {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (row >= model.connectedPeers.size()) {
|
||||
setFont(normal);
|
||||
setForeground(Color.LIGHT_GRAY);
|
||||
} else {
|
||||
if (model.connectedPeers.get(row).getDownloadData())
|
||||
setFont(bold);
|
||||
else
|
||||
setFont(normal);
|
||||
setForeground(Color.BLACK);
|
||||
|
||||
// Mark chain heights that aren't normal.
|
||||
if (column == PeerTableModel.CHAIN_HEIGHT) {
|
||||
long height = (Long) contents;
|
||||
if (height != peerGroup.getMostCommonChainHeight()) {
|
||||
setText(height + " • ");
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user