org.bitcoinj.core.Peer 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.
*/
// TODO: test peer with segwit
package org.bitcoinj.core;
import com.google.common.base.*;
import com.google.common.base.Objects;
import org.bitcoinj.core.listeners.*;
import org.bitcoinj.net.StreamConnection;
import org.bitcoinj.store.BlockStore;
import org.bitcoinj.store.BlockStoreException;
import org.bitcoinj.utils.ListenerRegistration;
import org.bitcoinj.utils.Threading;
import org.bitcoinj.wallet.Wallet;
import com.google.common.collect.Lists;
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.SettableFuture;
import net.jcip.annotations.GuardedBy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
/**
* A Peer handles the high level communication with a Bitcoin node, extending a {@link PeerSocketHandler} which
* handles low-level message (de)serialization.
*
* Note that timeouts are handled by the extended
* {@link org.bitcoinj.net.AbstractTimeoutHandler} and timeout is automatically disabled (using
* {@link org.bitcoinj.net.AbstractTimeoutHandler#setTimeoutEnabled(boolean)}) once the version
* handshake completes.
*/
public class Peer extends PeerSocketHandler {
private static final Logger log = LoggerFactory.getLogger(Peer.class);
protected final ReentrantLock lock = Threading.lock("peer");
private final NetworkParameters params;
private final AbstractBlockChain blockChain;
private final Context context;
private final CopyOnWriteArrayList> blocksDownloadedEventListeners
= new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList> chainDownloadStartedEventListeners
= new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList> connectedEventListeners
= new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList> disconnectedEventListeners
= new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList> getDataEventListeners
= new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList> preMessageReceivedEventListeners
= new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList> onTransactionEventListeners
= new CopyOnWriteArrayList<>();
// Whether to try and download blocks and transactions from this peer. Set to false by PeerGroup if not the
// primary peer. This is to avoid redundant work and concurrency problems with downloading the same chain
// in parallel.
private volatile boolean vDownloadData;
// The version data to announce to the other side of the connections we make: useful for setting our "user agent"
// equivalent and other things.
private final VersionMessage versionMessage;
// Maximum depth up to which pending transaction dependencies are downloaded, or 0 for disabled.
private volatile int vDownloadTxDependencyDepth;
// How many block messages the peer has announced to us. Peers only announce blocks that attach to their best chain
// so we can use this to calculate the height of the peers chain, by adding it to the initial height in the version
// message. This method can go wrong if the peer re-orgs onto a shorter (but harder) chain, however, this is rare.
private final AtomicInteger blocksAnnounced = new AtomicInteger();
// Each wallet added to the peer will be notified of downloaded transaction data.
private final CopyOnWriteArrayList wallets;
// A time before which we only download block headers, after that point we download block bodies.
@GuardedBy("lock") private long fastCatchupTimeSecs;
// Whether we are currently downloading headers only or block bodies. Starts at true. If the fast catchup time is
// set AND our best block is before that date, switch to false until block headers beyond that point have been
// received at which point it gets set to true again. This isn't relevant unless vDownloadData is true.
@GuardedBy("lock") private boolean downloadBlockBodies = true;
// Whether to request filtered blocks instead of full blocks if the protocol version allows for them.
@GuardedBy("lock") private boolean useFilteredBlocks = false;
// The current Bloom filter set on the connection, used to tell the remote peer what transactions to send us.
private volatile BloomFilter vBloomFilter;
// The last filtered block we received, we're waiting to fill it out with transactions.
private FilteredBlock currentFilteredBlock = null;
// How many filtered blocks have been received during the lifetime of this connection. Used to decide when to
// refresh the server-side side filter by sending a new one (it degrades over time as false positives are added
// on the remote side, see BIP 37 for a discussion of this).
// TODO: Is this still needed? It should not be since the auto FP tracking logic was added.
private int filteredBlocksReceived;
// If non-null, we should discard incoming filtered blocks because we ran out of keys and are awaiting a new filter
// to be calculated by the PeerGroup. The discarded block hashes should be added here so we can re-request them
// once we've recalculated and resent a new filter.
@GuardedBy("lock") @Nullable private List awaitingFreshFilter;
// How frequently to refresh the filter. This should become dynamic in future and calculated depending on the
// actual false positive rate. For now a good value was determined empirically around January 2013.
private static final int RESEND_BLOOM_FILTER_BLOCK_COUNT = 25000;
// Keeps track of things we requested internally with getdata but didn't receive yet, so we can avoid re-requests.
// It's not quite the same as getDataFutures, as this is used only for getdatas done as part of downloading
// the chain and so is lighter weight (we just keep a bunch of hashes not futures).
//
// It is important to avoid a nasty edge case where we can end up with parallel chain downloads proceeding
// simultaneously if we were to receive a newly solved block whilst parts of the chain are streaming to us.
private final HashSet pendingBlockDownloads = new HashSet<>();
// Keep references to TransactionConfidence objects for transactions that were announced by a remote peer, but
// which we haven't downloaded yet. These objects are de-duplicated by the TxConfidenceTable class.
// Once the tx is downloaded (by some peer), the Transaction object that is created will have a reference to
// the confidence object held inside it, and it's then up to the event listeners that receive the Transaction
// to keep it pinned to the root set if they care about this data.
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
private final HashSet pendingTxDownloads = new HashSet<>();
// The lowest version number we're willing to accept. Lower than this will result in an immediate disconnect.
private volatile int vMinProtocolVersion;
// When an API user explicitly requests a block or transaction from a peer, the InventoryItem is put here
// whilst waiting for the response. Is not used for downloads Peer generates itself.
private static class GetDataRequest {
public GetDataRequest(Sha256Hash hash, SettableFuture future) {
this.hash = hash;
this.future = future;
}
final Sha256Hash hash;
final SettableFuture future;
}
// TODO: The types/locking should be rationalised a bit.
private final CopyOnWriteArrayList getDataFutures;
@GuardedBy("getAddrFutures") private final LinkedList> getAddrFutures;
@Nullable @GuardedBy("lock") private LinkedList> getutxoFutures;
// Outstanding pings against this peer and how long the last one took to complete.
private final ReentrantLock lastPingTimesLock = new ReentrantLock();
@GuardedBy("lastPingTimesLock") private long[] lastPingTimes = null;
private final CopyOnWriteArrayList pendingPings;
private static final int PING_MOVING_AVERAGE_WINDOW = 20;
private volatile VersionMessage vPeerVersionMessage;
// A settable future which completes (with this) when the connection is open
private final SettableFuture connectionOpenFuture = SettableFuture.create();
private final SettableFuture outgoingVersionHandshakeFuture = SettableFuture.create();
private final SettableFuture incomingVersionHandshakeFuture = SettableFuture.create();
private final ListenableFuture versionHandshakeFuture = Futures.transform(
Futures.allAsList(outgoingVersionHandshakeFuture, incomingVersionHandshakeFuture),
new Function, Peer>() {
@Override
@Nullable
public Peer apply(@Nullable List peers) {
checkNotNull(peers);
checkState(peers.size() == 2 && peers.get(0) == peers.get(1));
return peers.get(0);
}
});
/**
* Construct a peer that reads/writes from the given block chain.
*
* Note that this does NOT make a connection to the given remoteAddress, it only creates a handler for a
* connection. If you want to create a one-off connection, create a Peer and pass it to
* {@link org.bitcoinj.net.NioClientManager#openConnection(java.net.SocketAddress, StreamConnection)}
* or
* {@link org.bitcoinj.net.NioClient#NioClient(java.net.SocketAddress, StreamConnection, int)}.
*
* The remoteAddress provided should match the remote address of the peer which is being connected to, and is
* used to keep track of which peers relayed transactions and offer more descriptive logging.
*/
public Peer(NetworkParameters params, VersionMessage ver, @Nullable AbstractBlockChain chain, PeerAddress remoteAddress) {
this(params, ver, remoteAddress, chain);
}
/**
* Construct a peer that reads/writes from the given block chain. Transactions stored in a {@link org.bitcoinj.core.TxConfidenceTable}
* will have their confidence levels updated when a peer announces it, to reflect the greater likelyhood that
* the transaction is valid.
*
* Note that this does NOT make a connection to the given remoteAddress, it only creates a handler for a
* connection. If you want to create a one-off connection, create a Peer and pass it to
* {@link org.bitcoinj.net.NioClientManager#openConnection(java.net.SocketAddress, StreamConnection)}
* or
* {@link org.bitcoinj.net.NioClient#NioClient(java.net.SocketAddress, StreamConnection, int)}.
*
* The remoteAddress provided should match the remote address of the peer which is being connected to, and is
* used to keep track of which peers relayed transactions and offer more descriptive logging.
*/
public Peer(NetworkParameters params, VersionMessage ver, PeerAddress remoteAddress,
@Nullable AbstractBlockChain chain) {
this(params, ver, remoteAddress, chain, Integer.MAX_VALUE);
}
/**
* Construct a peer that reads/writes from the given block chain. Transactions stored in a {@link org.bitcoinj.core.TxConfidenceTable}
* will have their confidence levels updated when a peer announces it, to reflect the greater likelyhood that
* the transaction is valid.
*
* Note that this does NOT make a connection to the given remoteAddress, it only creates a handler for a
* connection. If you want to create a one-off connection, create a Peer and pass it to
* {@link org.bitcoinj.net.NioClientManager#openConnection(java.net.SocketAddress, StreamConnection)}
* or
* {@link org.bitcoinj.net.NioClient#NioClient(java.net.SocketAddress, StreamConnection, int)}.
*
* The remoteAddress provided should match the remote address of the peer which is being connected to, and is
* used to keep track of which peers relayed transactions and offer more descriptive logging.
*/
public Peer(NetworkParameters params, VersionMessage ver, PeerAddress remoteAddress,
@Nullable AbstractBlockChain chain, int downloadTxDependencyDepth) {
super(params, remoteAddress);
this.params = Preconditions.checkNotNull(params);
this.versionMessage = Preconditions.checkNotNull(ver);
this.vDownloadTxDependencyDepth = chain != null ? downloadTxDependencyDepth : 0;
this.blockChain = chain; // Allowed to be null.
this.vDownloadData = chain != null;
this.getDataFutures = new CopyOnWriteArrayList<>();
this.getAddrFutures = new LinkedList<>();
this.fastCatchupTimeSecs = params.getGenesisBlock().getTimeSeconds();
this.pendingPings = new CopyOnWriteArrayList<>();
this.vMinProtocolVersion = params.getProtocolVersionNum(NetworkParameters.ProtocolVersion.PONG);
this.wallets = new CopyOnWriteArrayList<>();
this.context = Context.get();
this.versionHandshakeFuture.addListener(new Runnable() {
@Override
public void run() {
versionHandshakeComplete();
}
}, Threading.SAME_THREAD);
}
/**
* Construct a peer that reads/writes from the given chain. Automatically creates a VersionMessage for you from
* the given software name/version strings, which should be something like "MySimpleTool", "1.0" and which will tell
* the remote node to relay transaction inv messages before it has received a filter.
*
* Note that this does NOT make a connection to the given remoteAddress, it only creates a handler for a
* connection. If you want to create a one-off connection, create a Peer and pass it to
* {@link org.bitcoinj.net.NioClientManager#openConnection(java.net.SocketAddress, StreamConnection)}
* or
* {@link org.bitcoinj.net.NioClient#NioClient(java.net.SocketAddress, StreamConnection, int)}.
*
* The remoteAddress provided should match the remote address of the peer which is being connected to, and is
* used to keep track of which peers relayed transactions and offer more descriptive logging.
*/
public Peer(NetworkParameters params, AbstractBlockChain blockChain, PeerAddress peerAddress, String thisSoftwareName, String thisSoftwareVersion) {
this(params, new VersionMessage(params, blockChain.getBestChainHeight()), blockChain, peerAddress);
this.versionMessage.appendToSubVer(thisSoftwareName, thisSoftwareVersion, null);
}
/** Deprecated: use the more specific event handler methods instead */
@Deprecated @SuppressWarnings("deprecation")
public void addEventListener(AbstractPeerEventListener listener) {
addBlocksDownloadedEventListener(Threading.USER_THREAD, listener);
addChainDownloadStartedEventListener(Threading.USER_THREAD, listener);
addConnectedEventListener(Threading.USER_THREAD, listener);
addDisconnectedEventListener(Threading.USER_THREAD, listener);
addGetDataEventListener(Threading.USER_THREAD, listener);
addOnTransactionBroadcastListener(Threading.USER_THREAD, listener);
addPreMessageReceivedEventListener(Threading.USER_THREAD, listener);
}
/** Deprecated: use the more specific event handler methods instead */
@Deprecated
public void addEventListener(AbstractPeerEventListener listener, Executor executor) {
addBlocksDownloadedEventListener(executor, listener);
addChainDownloadStartedEventListener(executor, listener);
addConnectedEventListener(executor, listener);
addDisconnectedEventListener(executor, listener);
addGetDataEventListener(executor, listener);
addOnTransactionBroadcastListener(executor, listener);
addPreMessageReceivedEventListener(executor, listener);
}
/** Deprecated: use the more specific event handler methods instead */
@Deprecated
public void removeEventListener(AbstractPeerEventListener listener) {
removeBlocksDownloadedEventListener(listener);
removeChainDownloadStartedEventListener(listener);
removeConnectedEventListener(listener);
removeDisconnectedEventListener(listener);
removeGetDataEventListener(listener);
removeOnTransactionBroadcastListener(listener);
removePreMessageReceivedEventListener(listener);
}
/** Registers a listener that is invoked when new blocks are downloaded. */
public void addBlocksDownloadedEventListener(BlocksDownloadedEventListener listener) {
addBlocksDownloadedEventListener(Threading.USER_THREAD, listener);
}
/** Registers a listener that is invoked when new blocks are downloaded. */
public void addBlocksDownloadedEventListener(Executor executor, BlocksDownloadedEventListener listener) {
blocksDownloadedEventListeners.add(new ListenerRegistration(listener, executor));
}
/** Registers a listener that is invoked when a blockchain downloaded starts. */
public void addChainDownloadStartedEventListener(ChainDownloadStartedEventListener listener) {
addChainDownloadStartedEventListener(Threading.USER_THREAD, listener);
}
/** Registers a listener that is invoked when a blockchain downloaded starts. */
public void addChainDownloadStartedEventListener(Executor executor, ChainDownloadStartedEventListener listener) {
chainDownloadStartedEventListeners.add(new ListenerRegistration(listener, executor));
}
/** Registers a listener that is invoked when a peer is connected. */
public void addConnectedEventListener(PeerConnectedEventListener listener) {
addConnectedEventListener(Threading.USER_THREAD, listener);
}
/** Registers a listener that is invoked when a peer is connected. */
public void addConnectedEventListener(Executor executor, PeerConnectedEventListener listener) {
connectedEventListeners.add(new ListenerRegistration(listener, executor));
}
/** Registers a listener that is invoked when a peer is disconnected. */
public void addDisconnectedEventListener(PeerDisconnectedEventListener listener) {
addDisconnectedEventListener(Threading.USER_THREAD, listener);
}
/** Registers a listener that is invoked when a peer is disconnected. */
public void addDisconnectedEventListener(Executor executor, PeerDisconnectedEventListener listener) {
disconnectedEventListeners.add(new ListenerRegistration(listener, executor));
}
/** Registers a listener that is called when messages are received. */
public void addGetDataEventListener(GetDataEventListener listener) {
addGetDataEventListener(Threading.USER_THREAD, listener);
}
/** Registers a listener that is called when messages are received. */
public void addGetDataEventListener(Executor executor, GetDataEventListener listener) {
getDataEventListeners.add(new ListenerRegistration<>(listener, executor));
}
/** Registers a listener that is called when a transaction is broadcast across the network */
public void addOnTransactionBroadcastListener(OnTransactionBroadcastListener listener) {
addOnTransactionBroadcastListener(Threading.USER_THREAD, listener);
}
/** Registers a listener that is called when a transaction is broadcast across the network */
public void addOnTransactionBroadcastListener(Executor executor, OnTransactionBroadcastListener listener) {
onTransactionEventListeners.add(new ListenerRegistration<>(listener, executor));
}
/** Registers a listener that is called immediately before a message is received */
public void addPreMessageReceivedEventListener(PreMessageReceivedEventListener listener) {
addPreMessageReceivedEventListener(Threading.USER_THREAD, listener);
}
/** Registers a listener that is called immediately before a message is received */
public void addPreMessageReceivedEventListener(Executor executor, PreMessageReceivedEventListener listener) {
preMessageReceivedEventListeners.add(new ListenerRegistration<>(listener, executor));
}
public boolean removeBlocksDownloadedEventListener(BlocksDownloadedEventListener listener) {
return ListenerRegistration.removeFromList(listener, blocksDownloadedEventListeners);
}
public boolean removeChainDownloadStartedEventListener(ChainDownloadStartedEventListener listener) {
return ListenerRegistration.removeFromList(listener, chainDownloadStartedEventListeners);
}
public boolean removeConnectedEventListener(PeerConnectedEventListener listener) {
return ListenerRegistration.removeFromList(listener, connectedEventListeners);
}
public boolean removeDisconnectedEventListener(PeerDisconnectedEventListener listener) {
return ListenerRegistration.removeFromList(listener, disconnectedEventListeners);
}
public boolean removeGetDataEventListener(GetDataEventListener listener) {
return ListenerRegistration.removeFromList(listener, getDataEventListeners);
}
public boolean removeOnTransactionBroadcastListener(OnTransactionBroadcastListener listener) {
return ListenerRegistration.removeFromList(listener, onTransactionEventListeners);
}
public boolean removePreMessageReceivedEventListener(PreMessageReceivedEventListener listener) {
return ListenerRegistration.removeFromList(listener, preMessageReceivedEventListeners);
}
@Override
public String toString() {
PeerAddress addr = getAddress();
// if null, it's a user-provided NetworkConnection object
return addr == null ? "Peer()" : addr.toString();
}
@Override
protected void timeoutOccurred() {
super.timeoutOccurred();
if (!connectionOpenFuture.isDone()) {
connectionClosed(); // Invoke the event handlers to tell listeners e.g. PeerGroup that we never managed to connect.
}
}
@Override
public void connectionClosed() {
for (final ListenerRegistration registration : disconnectedEventListeners) {
registration.executor.execute(new Runnable() {
@Override
public void run() {
registration.listener.onPeerDisconnected(Peer.this, 0);
}
});
}
}
@Override
public void connectionOpened() {
// Announce ourselves. This has to come first to connect to clients beyond v0.3.20.2 which wait to hear
// from us until they send their version message back.
PeerAddress address = getAddress();
log.info("Announcing to {} as: {}", address == null ? "Peer" : address.toSocketAddress(), versionMessage.subVer);
sendMessage(versionMessage);
connectionOpenFuture.set(this);
// When connecting, the remote peer sends us a version message with various bits of
// useful data in it. We need to know the peer protocol version before we can talk to it.
}
/**
* Provides a ListenableFuture that can be used to wait for the socket to connect. A socket connection does not
* mean that protocol handshake has occurred.
*/
public ListenableFuture getConnectionOpenFuture() {
return connectionOpenFuture;
}
public ListenableFuture getVersionHandshakeFuture() {
return versionHandshakeFuture;
}
@Override
protected void processMessage(Message m) throws Exception {
// Allow event listeners to filter the message stream. Listeners are allowed to drop messages by
// returning null.
for (ListenerRegistration registration : preMessageReceivedEventListeners) {
// Skip any listeners that are supposed to run in another thread as we don't want to block waiting
// for it, which might cause circular deadlock.
if (registration.executor == Threading.SAME_THREAD) {
m = registration.listener.onPreMessageReceived(this, m);
if (m == null) break;
}
}
if (m == null) return;
// If we are in the middle of receiving transactions as part of a filtered block push from the remote node,
// and we receive something that's not a transaction, then we're done.
if (currentFilteredBlock != null && !(m instanceof Transaction)) {
endFilteredBlock(currentFilteredBlock);
currentFilteredBlock = null;
}
// No further communication is possible until version handshake is complete.
if (!(m instanceof VersionMessage || m instanceof VersionAck
|| (versionHandshakeFuture.isDone() && !versionHandshakeFuture.isCancelled())))
throw new ProtocolException(
"Received " + m.getClass().getSimpleName() + " before version handshake is complete.");
if (m instanceof Ping) {
processPing((Ping) m);
} else if (m instanceof Pong) {
processPong((Pong) m);
} else if (m instanceof NotFoundMessage) {
// This is sent to us when we did a getdata on some transactions that aren't in the peers memory pool.
// Because NotFoundMessage is a subclass of InventoryMessage, the test for it must come before the next.
processNotFoundMessage((NotFoundMessage) m);
} else if (m instanceof InventoryMessage) {
processInv((InventoryMessage) m);
} else if (m instanceof Block) {
processBlock((Block) m);
} else if (m instanceof FilteredBlock) {
startFilteredBlock((FilteredBlock) m);
} else if (m instanceof Transaction) {
processTransaction((Transaction) m);
} else if (m instanceof GetDataMessage) {
processGetData((GetDataMessage) m);
} else if (m instanceof AddressMessage) {
// We don't care about addresses of the network right now. But in future,
// we should save them in the wallet so we don't put too much load on the seed nodes and can
// properly explore the network.
processAddressMessage((AddressMessage) m);
} else if (m instanceof HeadersMessage) {
processHeaders((HeadersMessage) m);
} else if (m instanceof AlertMessage) {
processAlert((AlertMessage) m);
} else if (m instanceof VersionMessage) {
processVersionMessage((VersionMessage) m);
} else if (m instanceof VersionAck) {
processVersionAck((VersionAck) m);
} else if (m instanceof UTXOsMessage) {
processUTXOMessage((UTXOsMessage) m);
} else if (m instanceof RejectMessage) {
log.error("{} {}: Received {}", this, getPeerVersionMessage().subVer, m);
} else {
log.warn("{}: Received unhandled message: {}", this, m);
}
}
protected void processUTXOMessage(UTXOsMessage m) {
SettableFuture future = null;
lock.lock();
try {
if (getutxoFutures != null)
future = getutxoFutures.pollFirst();
} finally {
lock.unlock();
}
if (future != null)
future.set(m);
}
private void processAddressMessage(AddressMessage m) {
SettableFuture future;
synchronized (getAddrFutures) {
future = getAddrFutures.poll();
if (future == null) // Not an addr message we are waiting for.
return;
}
future.set(m);
}
private void processVersionMessage(VersionMessage m) throws ProtocolException {
if (vPeerVersionMessage != null)
throw new ProtocolException("Got two version messages from peer");
vPeerVersionMessage = m;
// Switch to the new protocol version.
long peerTime = vPeerVersionMessage.time * 1000;
log.info("{}: Got version={}, subVer='{}', services=0x{}, time={}, blocks={}",
this,
vPeerVersionMessage.clientVersion,
vPeerVersionMessage.subVer,
vPeerVersionMessage.localServices,
String.format(Locale.US, "%tF %tT", peerTime, peerTime),
vPeerVersionMessage.bestHeight);
// bitcoinj is a client mode implementation. That means there's not much point in us talking to other client
// mode nodes because we can't download the data from them we need to find/verify transactions. Some bogus
// implementations claim to have a block chain in their services field but then report a height of zero, filter
// them out here.
if (!vPeerVersionMessage.hasBlockChain() ||
(!params.allowEmptyPeerChain() && vPeerVersionMessage.bestHeight == 0)) {
// Shut down the channel gracefully.
log.info("{}: Peer does not have a copy of the block chain.", this);
close();
return;
}
if ((vPeerVersionMessage.localServices
& VersionMessage.NODE_BITCOIN_CASH) == VersionMessage.NODE_BITCOIN_CASH) {
log.info("{}: Peer follows an incompatible block chain.", this);
// Shut down the channel gracefully.
close();
return;
}
if (vPeerVersionMessage.bestHeight < 0)
// In this case, it's a protocol violation.
throw new ProtocolException("Peer reports invalid best height: " + vPeerVersionMessage.bestHeight);
// Now it's our turn ...
// Send an ACK message stating we accept the peers protocol version.
sendMessage(new VersionAck());
log.debug("{}: Incoming version handshake complete.", this);
incomingVersionHandshakeFuture.set(this);
}
private void processVersionAck(VersionAck m) throws ProtocolException {
if (vPeerVersionMessage == null) {
throw new ProtocolException("got a version ack before version");
}
if (outgoingVersionHandshakeFuture.isDone()) {
throw new ProtocolException("got more than one version ack");
}
log.debug("{}: Outgoing version handshake complete.", this);
outgoingVersionHandshakeFuture.set(this);
}
private void versionHandshakeComplete() {
log.debug("{}: Handshake complete.", this);
setTimeoutEnabled(false);
for (final ListenerRegistration registration : connectedEventListeners) {
registration.executor.execute(new Runnable() {
@Override
public void run() {
registration.listener.onPeerConnected(Peer.this, 1);
}
});
}
// We check min version after onPeerConnected as channel.close() will
// call onPeerDisconnected, and we should probably call onPeerConnected first.
final int version = vMinProtocolVersion;
if (vPeerVersionMessage.clientVersion < version) {
log.warn("Connected to a peer speaking protocol version {} but need {}, closing",
vPeerVersionMessage.clientVersion, version);
close();
}
}
protected void startFilteredBlock(FilteredBlock m) {
// Filtered blocks come before the data that they refer to, so stash it here and then fill it out as
// messages stream in. We'll call endFilteredBlock when a non-tx message arrives (eg, another
// FilteredBlock) or when a tx that isn't needed by that block is found. A ping message is sent after
// a getblocks, to force the non-tx message path.
currentFilteredBlock = m;
// Potentially refresh the server side filter. Because the remote node adds hits back into the filter
// to save round-tripping back through us, the filter degrades over time as false positives get added,
// triggering yet more false positives. We refresh it every so often to get the FP rate back down.
filteredBlocksReceived++;
if (filteredBlocksReceived % RESEND_BLOOM_FILTER_BLOCK_COUNT == RESEND_BLOOM_FILTER_BLOCK_COUNT - 1) {
sendMessage(vBloomFilter);
}
}
protected void processNotFoundMessage(NotFoundMessage m) {
// This is received when we previously did a getdata but the peer couldn't find what we requested in it's
// memory pool. Typically, because we are downloading dependencies of a relevant transaction and reached
// the bottom of the dependency tree (where the unconfirmed transactions connect to transactions that are
// in the chain).
//
// We go through and cancel the pending getdata futures for the items we were told weren't found.
for (GetDataRequest req : getDataFutures) {
for (InventoryItem item : m.getItems()) {
if (item.hash.equals(req.hash)) {
log.info("{}: Bottomed out dep tree at {}", this, req.hash);
req.future.cancel(true);
getDataFutures.remove(req);
break;
}
}
}
}
protected void processAlert(AlertMessage m) {
try {
if (m.isSignatureValid()) {
log.debug("Received alert from peer {}: {}", this, m.getStatusBar());
} else {
log.debug("Received alert with invalid signature from peer {}: {}", this, m.getStatusBar());
}
} catch (Throwable t) {
// Signature checking can FAIL on Android platforms before Gingerbread apparently due to bugs in their
// BigInteger implementations! See https://github.com/bitcoinj/bitcoinj/issues/526 for discussion. As
// alerts are just optional and not that useful, we just swallow the error here.
log.error("Failed to check signature: bug in platform libraries?", t);
}
}
protected void processHeaders(HeadersMessage m) throws ProtocolException {
// Runs in network loop thread for this peer.
//
// This method can run if a peer just randomly sends us a "headers" message (should never happen), or more
// likely when we've requested them as part of chain download using fast catchup. We need to add each block to
// the chain if it pre-dates the fast catchup time. If we go past it, we can stop processing the headers and
// request the full blocks from that point on instead.
boolean downloadBlockBodies;
long fastCatchupTimeSecs;
lock.lock();
try {
if (blockChain == null) {
// Can happen if we are receiving unrequested data, or due to programmer error.
log.warn("Received headers when Peer is not configured with a chain.");
return;
}
fastCatchupTimeSecs = this.fastCatchupTimeSecs;
downloadBlockBodies = this.downloadBlockBodies;
} finally {
lock.unlock();
}
try {
checkState(!downloadBlockBodies, toString());
for (int i = 0; i < m.getBlockHeaders().size(); i++) {
Block header = m.getBlockHeaders().get(i);
// Process headers until we pass the fast catchup time, or are about to catch up with the head
// of the chain - always process the last block as a full/filtered block to kick us out of the
// fast catchup mode (in which we ignore new blocks).
boolean passedTime = header.getTimeSeconds() >= fastCatchupTimeSecs;
boolean reachedTop = blockChain.getBestChainHeight() >= vPeerVersionMessage.bestHeight;
if (!passedTime && !reachedTop) {
if (!vDownloadData) {
// Not download peer anymore, some other peer probably became better.
log.info("Lost download peer status, throwing away downloaded headers.");
return;
}
if (blockChain.add(header)) {
// The block was successfully linked into the chain. Notify the user of our progress.
invokeOnBlocksDownloaded(header, null);
} else {
// This block is unconnected - we don't know how to get from it back to the genesis block yet.
// That must mean that the peer is buggy or malicious because we specifically requested for
// headers that are part of the best chain.
throw new ProtocolException("Got unconnected header from peer: " + header.getHashAsString());
}
} else {
lock.lock();
try {
log.info(
"Passed the fast catchup time ({}) at height {}, discarding {} headers and requesting full blocks",
Utils.dateTimeFormat(fastCatchupTimeSecs * 1000), blockChain.getBestChainHeight() + 1,
m.getBlockHeaders().size() - i);
this.downloadBlockBodies = true;
// Prevent this request being seen as a duplicate.
this.lastGetBlocksBegin = Sha256Hash.ZERO_HASH;
blockChainDownloadLocked(Sha256Hash.ZERO_HASH);
} finally {
lock.unlock();
}
return;
}
}
// We added all headers in the message to the chain. Request some more if we got up to the limit, otherwise
// we are at the end of the chain.
if (m.getBlockHeaders().size() >= HeadersMessage.MAX_HEADERS) {
lock.lock();
try {
blockChainDownloadLocked(Sha256Hash.ZERO_HASH);
} finally {
lock.unlock();
}
}
} catch (VerificationException e) {
log.warn("Block header verification failed", e);
} catch (PrunedException e) {
// Unreachable when in SPV mode.
throw new RuntimeException(e);
}
}
protected void processGetData(GetDataMessage getdata) {
log.info("{}: Received getdata message: {}", getAddress(), getdata.toString());
ArrayList items = new ArrayList<>();
for (ListenerRegistration registration : getDataEventListeners) {
if (registration.executor != Threading.SAME_THREAD) continue;
List listenerItems = registration.listener.getData(this, getdata);
if (listenerItems == null) continue;
items.addAll(listenerItems);
}
if (items.isEmpty()) {
return;
}
log.info("{}: Sending {} items gathered from listeners to peer", getAddress(), items.size());
for (Message item : items) {
sendMessage(item);
}
}
protected void processTransaction(final Transaction tx) throws VerificationException {
// Check a few basic syntax issues to ensure the received TX isn't nonsense.
tx.verify();
lock.lock();
try {
log.debug("{}: Received tx {}", getAddress(), tx.getHashAsString());
// Label the transaction as coming in from the P2P network (as opposed to being created by us, direct import,
// etc). This helps the wallet decide how to risk analyze it later.
//
// Additionally, by invoking tx.getConfidence(), this tx now pins the confidence data into the heap, meaning
// we can stop holding a reference to the confidence object ourselves. It's up to event listeners on the
// Peer to stash the tx object somewhere if they want to keep receiving updates about network propagation
// and so on.
TransactionConfidence confidence = tx.getConfidence();
confidence.setSource(TransactionConfidence.Source.NETWORK);
pendingTxDownloads.remove(confidence);
if (maybeHandleRequestedData(tx)) {
return;
}
if (currentFilteredBlock != null) {
if (!currentFilteredBlock.provideTransaction(tx)) {
// Got a tx that didn't fit into the filtered block, so we must have received everything.
endFilteredBlock(currentFilteredBlock);
currentFilteredBlock = null;
}
// Don't tell wallets or listeners about this tx as they'll learn about it when the filtered block is
// fully downloaded instead.
return;
}
// It's a broadcast transaction. Tell all wallets about this tx so they can check if it's relevant or not.
for (final Wallet wallet : wallets) {
try {
if (wallet.isPendingTransactionRelevant(tx)) {
if (vDownloadTxDependencyDepth > 0) {
// This transaction seems interesting to us, so let's download its dependencies. This has
// several purposes: we can check that the sender isn't attacking us by engaging in protocol
// abuse games, like depending on a time-locked transaction that will never confirm, or
// building huge chains of unconfirmed transactions (again - so they don't confirm and the
// money can be taken back with a Finney attack). Knowing the dependencies also lets us
// store them in a serialized wallet so we always have enough data to re-announce to the
// network and get the payment into the chain, in case the sender goes away and the network
// starts to forget.
//
// TODO: Not all the above things are implemented.
//
// Note that downloading of dependencies can end up walking around 15 minutes back even
// through transactions that have confirmed, as getdata on the remote peer also checks
// relay memory not only the mempool. Unfortunately we have no way to know that here. In
// practice it should not matter much.
Futures.addCallback(downloadDependencies(tx), new FutureCallback>() {
@Override
public void onSuccess(List dependencies) {
try {
log.info("{}: Dependency download complete!", getAddress());
wallet.receivePending(tx, dependencies);
} catch (VerificationException e) {
log.error("{}: Wallet failed to process pending transaction {}", getAddress(), tx.getHash());
log.error("Error was: ", e);
// Not much more we can do at this point.
}
}
@Override
public void onFailure(Throwable throwable) {
log.error("Could not download dependencies of tx {}", tx.getHashAsString());
log.error("Error was: ", throwable);
// Not much more we can do at this point.
}
});
} else {
wallet.receivePending(tx, null);
}
}
} catch (VerificationException e) {
log.error("Wallet failed to verify tx", e);
// Carry on, listeners may still want to know.
}
}
} finally {
lock.unlock();
}
// Tell all listeners about this tx so they can decide whether to keep it or not. If no listener keeps a
// reference around then the memory pool will forget about it after a while too because it uses weak references.
for (final ListenerRegistration registration : onTransactionEventListeners) {
registration.executor.execute(new Runnable() {
@Override
public void run() {
registration.listener.onTransaction(Peer.this, tx);
}
});
}
}
/**
* Returns a future that wraps a list of all transactions that the given transaction depends on, recursively.
* Only transactions in peers memory pools are included; the recursion stops at transactions that are in the
* current best chain. So it doesn't make much sense to provide a tx that was already in the best chain and
* a precondition checks this.
*
* For example, if tx has 2 inputs that connect to transactions A and B, and transaction B is unconfirmed and
* has one input connecting to transaction C that is unconfirmed, and transaction C connects to transaction D
* that is in the chain, then this method will return either {B, C} or {C, B}. No ordering is guaranteed.
*
* This method is useful for apps that want to learn about how long an unconfirmed transaction might take
* to confirm, by checking for unexpectedly time locked transactions, unusually deep dependency trees or fee-paying
* transactions that depend on unconfirmed free transactions.
*
* Note that dependencies downloaded this way will not trigger the onTransaction method of event listeners.
*/
public ListenableFuture> downloadDependencies(Transaction tx) {
TransactionConfidence.ConfidenceType txConfidence = tx.getConfidence().getConfidenceType();
Preconditions.checkArgument(txConfidence != TransactionConfidence.ConfidenceType.BUILDING);
log.info("{}: Downloading dependencies of {}", getAddress(), tx.getHashAsString());
final LinkedList results = new LinkedList<>();
// future will be invoked when the entire dependency tree has been walked and the results compiled.
final ListenableFuture