com.google.bitcoin.core.PeerGroup Maven / Gradle / Ivy
/**
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.bitcoin.core;
import com.google.bitcoin.net.ClientConnectionManager;
import com.google.bitcoin.net.FilterMerger;
import com.google.bitcoin.net.NioClientManager;
import com.google.bitcoin.net.discovery.PeerDiscovery;
import com.google.bitcoin.net.discovery.PeerDiscoveryException;
import com.google.bitcoin.script.Script;
import com.google.bitcoin.utils.ExponentialBackoff;
import com.google.bitcoin.utils.ListenerRegistration;
import com.google.bitcoin.utils.Threading;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.*;
import net.jcip.annotations.GuardedBy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
/**
* Runs a set of connections to the P2P network, brings up connections to replace disconnected nodes and manages
* the interaction between them all. Most applications will want to use one of these.
*
* PeerGroup tries to maintain a constant number of connections to a set of distinct peers.
* Each peer runs a network listener in its own thread. When a connection is lost, a new peer
* will be tried after a delay as long as the number of connections less than the maximum.
*
* Connections are made to addresses from a provided list. When that list is exhausted,
* we start again from the head of the list.
*
* The PeerGroup can broadcast a transaction to the currently connected set of peers. It can
* also handle download of the blockchain from peers, restarting the process when peers die.
*
* PeerGroup implements the {@link Service} interface. This means before it will do anything,
* you must call the {@link com.google.common.util.concurrent.Service#start()} method (which returns
* a future) or {@link com.google.common.util.concurrent.Service#startAndWait()} method, which will block
* until peer discovery is completed and some outbound connections have been initiated (it will return
* before handshaking is done, however). You should call {@link com.google.common.util.concurrent.Service#stop()}
* when finished. Note that not all methods of PeerGroup are safe to call from a UI thread as some may do
* network IO, but starting and stopping the service should be fine.
*/
public class PeerGroup extends AbstractExecutionThreadService implements TransactionBroadcaster {
private static final int DEFAULT_CONNECTIONS = 4;
private static final Logger log = LoggerFactory.getLogger(PeerGroup.class);
protected final ReentrantLock lock = Threading.lock("peergroup");
// Addresses to try to connect to, excluding active peers.
@GuardedBy("lock") private final PriorityQueue inactives;
@GuardedBy("lock") private final Map backoffMap;
// Currently active peers. This is an ordered list rather than a set to make unit tests predictable.
private final CopyOnWriteArrayList peers;
// Currently connecting peers.
private final CopyOnWriteArrayList pendingPeers;
private final ClientConnectionManager channels;
// The peer that has been selected for the purposes of downloading announced data.
@GuardedBy("lock") private Peer downloadPeer;
// Callback for events related to chain download
@Nullable @GuardedBy("lock") private PeerEventListener downloadListener;
// Callbacks for events related to peer connection/disconnection
private final CopyOnWriteArrayList> peerEventListeners;
// Peer discovery sources, will be polled occasionally if there aren't enough inactives.
private final CopyOnWriteArraySet peerDiscoverers;
// The version message to use for new connections.
@GuardedBy("lock") private VersionMessage versionMessage;
// A class that tracks recent transactions that have been broadcast across the network, counts how many
// peers announced them and updates the transaction confidence data. It is passed to each Peer.
private final MemoryPool memoryPool;
// How many connections we want to have open at the current time. If we lose connections, we'll try opening more
// until we reach this count.
@GuardedBy("lock") private int maxConnections;
// Minimum protocol version we will allow ourselves to connect to: require Bloom filtering.
private volatile int vMinRequiredProtocolVersion = FilteredBlock.MIN_PROTOCOL_VERSION;
// Runs a background thread that we use for scheduling pings to our peers, so we can measure their performance
// and network latency. We ping peers every pingIntervalMsec milliseconds.
private volatile Timer vPingTimer;
/** How many milliseconds to wait after receiving a pong before sending another ping. */
public static final long DEFAULT_PING_INTERVAL_MSEC = 2000;
private long pingIntervalMsec = DEFAULT_PING_INTERVAL_MSEC;
private final NetworkParameters params;
private final AbstractBlockChain chain;
@GuardedBy("lock") private long fastCatchupTimeSecs;
private final CopyOnWriteArrayList wallets;
private final CopyOnWriteArrayList peerFilterProviders;
// This event listener is added to every peer. It's here so when we announce transactions via an "inv", every
// peer can fetch them.
private final AbstractPeerEventListener peerListener = new AbstractPeerEventListener() {
@Override
public List getData(Peer peer, GetDataMessage m) {
return handleGetData(m);
}
@Override
public void onBlocksDownloaded(Peer peer, Block block, int blocksLeft) {
double rate = checkNotNull(chain).getFalsePositiveRate();
if (rate > bloomFilterMerger.getBloomFilterFPRate() * MAX_FP_RATE_INCREASE) {
log.info("Force update Bloom filter due to high false positive rate");
recalculateFastCatchupAndFilter(FilterRecalculateMode.FORCE_SEND);
}
}
};
private int minBroadcastConnections = 0;
private Runnable bloomSendIfChanged = new Runnable() {
@Override public void run() {
recalculateFastCatchupAndFilter(FilterRecalculateMode.SEND_IF_CHANGED);
}
};
private Runnable bloomDontSend = new Runnable() {
@Override public void run() {
recalculateFastCatchupAndFilter(FilterRecalculateMode.DONT_SEND);
}
};
private AbstractWalletEventListener walletEventListener = new AbstractWalletEventListener() {
private void queueRecalc(boolean andTransmit) {
if (andTransmit) {
log.info("Queuing recalc of the Bloom filter due to new keys or scripts becoming available");
Uninterruptibles.putUninterruptibly(jobQueue, bloomSendIfChanged);
} else {
log.info("Queuing recalc of the Bloom filter due to observing a pay to pubkey output on a relevant tx");
Uninterruptibles.putUninterruptibly(jobQueue, bloomDontSend);
}
}
@Override public void onScriptsAdded(Wallet wallet, List