mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-11-02 21:47:18 +00:00
Peer: Configure maximum recursion level when requesting dependent transactions.
The idea is to limit download to a sane amount, rather than disabling it completely.
This commit is contained in:
@@ -83,8 +83,8 @@ public class Peer extends PeerSocketHandler {
|
||||
// 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;
|
||||
// Switch for enabling download of pending transaction dependencies.
|
||||
private volatile boolean vDownloadTxDependencies;
|
||||
// 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.
|
||||
@@ -191,7 +191,7 @@ public class Peer extends PeerSocketHandler {
|
||||
*/
|
||||
public Peer(NetworkParameters params, VersionMessage ver, PeerAddress remoteAddress,
|
||||
@Nullable AbstractBlockChain chain) {
|
||||
this(params, ver, remoteAddress, chain, true);
|
||||
this(params, ver, remoteAddress, chain, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,11 +209,11 @@ public class Peer extends PeerSocketHandler {
|
||||
* used to keep track of which peers relayed transactions and offer more descriptive logging.</p>
|
||||
*/
|
||||
public Peer(NetworkParameters params, VersionMessage ver, PeerAddress remoteAddress,
|
||||
@Nullable AbstractBlockChain chain, boolean downloadTxDependencies) {
|
||||
@Nullable AbstractBlockChain chain, int downloadTxDependencyDepth) {
|
||||
super(params, remoteAddress);
|
||||
this.params = Preconditions.checkNotNull(params);
|
||||
this.versionMessage = Preconditions.checkNotNull(ver);
|
||||
this.vDownloadTxDependencies = chain != null && downloadTxDependencies;
|
||||
this.vDownloadTxDependencyDepth = chain != null ? downloadTxDependencyDepth : 0;
|
||||
this.blockChain = chain; // Allowed to be null.
|
||||
this.vDownloadData = chain != null;
|
||||
this.getDataFutures = new CopyOnWriteArrayList<GetDataRequest>();
|
||||
@@ -754,7 +754,7 @@ public class Peer extends PeerSocketHandler {
|
||||
for (final Wallet wallet : wallets) {
|
||||
try {
|
||||
if (wallet.isPendingTransactionRelevant(tx)) {
|
||||
if (vDownloadTxDependencies) {
|
||||
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
|
||||
@@ -836,7 +836,8 @@ public class Peer extends PeerSocketHandler {
|
||||
log.info("{}: Downloading dependencies of {}", getAddress(), tx.getHashAsString());
|
||||
final LinkedList<Transaction> results = new LinkedList<Transaction>();
|
||||
// future will be invoked when the entire dependency tree has been walked and the results compiled.
|
||||
final ListenableFuture<Object> future = downloadDependenciesInternal(tx, new Object(), results);
|
||||
final ListenableFuture<Object> future = downloadDependenciesInternal(vDownloadTxDependencyDepth, 0, tx,
|
||||
new Object(), results);
|
||||
final SettableFuture<List<Transaction>> resultFuture = SettableFuture.create();
|
||||
Futures.addCallback(future, new FutureCallback<Object>() {
|
||||
@Override
|
||||
@@ -853,9 +854,9 @@ public class Peer extends PeerSocketHandler {
|
||||
}
|
||||
|
||||
// The marker object in the future returned is the same as the parameter. It is arbitrary and can be anything.
|
||||
protected ListenableFuture<Object> downloadDependenciesInternal(final Transaction tx,
|
||||
final Object marker,
|
||||
final List<Transaction> results) {
|
||||
protected ListenableFuture<Object> downloadDependenciesInternal(final int maxDepth, final int depth,
|
||||
final Transaction tx, final Object marker, final List<Transaction> results) {
|
||||
|
||||
final SettableFuture<Object> resultFuture = SettableFuture.create();
|
||||
final Sha256Hash rootTxHash = tx.getHash();
|
||||
// We want to recursively grab its dependencies. This is so listeners can learn important information like
|
||||
@@ -874,7 +875,7 @@ public class Peer extends PeerSocketHandler {
|
||||
List<ListenableFuture<Transaction>> futures = Lists.newArrayList();
|
||||
GetDataMessage getdata = new GetDataMessage(params);
|
||||
if (needToRequest.size() > 1)
|
||||
log.info("{}: Requesting {} transactions for dep resolution", getAddress(), needToRequest.size());
|
||||
log.info("{}: Requesting {} transactions for depth {} dep resolution", getAddress(), needToRequest.size(), depth + 1);
|
||||
for (Sha256Hash hash : needToRequest) {
|
||||
getdata.addTransaction(hash);
|
||||
GetDataRequest req = new GetDataRequest(hash, SettableFuture.create());
|
||||
@@ -893,7 +894,8 @@ public class Peer extends PeerSocketHandler {
|
||||
log.info("{}: Downloaded dependency of {}: {}", getAddress(), rootTxHash, tx.getHashAsString());
|
||||
results.add(tx);
|
||||
// Now recurse into the dependencies of this transaction too.
|
||||
childFutures.add(downloadDependenciesInternal(tx, marker, results));
|
||||
if (depth + 1 < maxDepth)
|
||||
childFutures.add(downloadDependenciesInternal(maxDepth, depth + 1, tx, marker, results));
|
||||
}
|
||||
if (childFutures.size() == 0) {
|
||||
// Short-circuit: we're at the bottom of this part of the tree.
|
||||
@@ -1792,7 +1794,7 @@ public class Peer extends PeerSocketHandler {
|
||||
* to try and discover if a pending tx might be at risk of double spending.
|
||||
*/
|
||||
public boolean isDownloadTxDependencies() {
|
||||
return vDownloadTxDependencies;
|
||||
return vDownloadTxDependencyDepth > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1800,7 +1802,16 @@ public class Peer extends PeerSocketHandler {
|
||||
* before handing the transaction off to the wallet. The wallet can do risk analysis on pending/recent transactions
|
||||
* to try and discover if a pending tx might be at risk of double spending.
|
||||
*/
|
||||
public void setDownloadTxDependencies(boolean value) {
|
||||
vDownloadTxDependencies = value;
|
||||
public void setDownloadTxDependencies(boolean enable) {
|
||||
vDownloadTxDependencyDepth = enable ? Integer.MAX_VALUE : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if this peer will use getdata/notfound messages to walk backwards through transaction dependencies
|
||||
* before handing the transaction off to the wallet. The wallet can do risk analysis on pending/recent transactions
|
||||
* to try and discover if a pending tx might be at risk of double spending.
|
||||
*/
|
||||
public void setDownloadTxDependencies(int depth) {
|
||||
vDownloadTxDependencyDepth = depth;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,8 +140,8 @@ public class PeerGroup implements TransactionBroadcaster {
|
||||
private final CopyOnWriteArraySet<PeerDiscovery> peerDiscoverers;
|
||||
// The version message to use for new connections.
|
||||
@GuardedBy("lock") private VersionMessage versionMessage;
|
||||
// Switch for enabling download of pending transaction dependencies.
|
||||
@GuardedBy("lock") protected boolean downloadTxDependencies;
|
||||
// Maximum depth up to which pending transaction dependencies are downloaded, or 0 for disabled.
|
||||
@GuardedBy("lock") private int downloadTxDependencyDepth;
|
||||
// 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;
|
||||
@@ -413,7 +413,7 @@ public class PeerGroup implements TransactionBroadcaster {
|
||||
// We never request that the remote node wait for a bloom filter yet, as we have no wallets
|
||||
versionMessage.relayTxesBeforeFilter = true;
|
||||
|
||||
downloadTxDependencies = true;
|
||||
downloadTxDependencyDepth = Integer.MAX_VALUE;
|
||||
|
||||
inactives = new PriorityQueue<PeerAddress>(1, new Comparator<PeerAddress>() {
|
||||
@SuppressWarnings("FieldAccessNotGuarded") // only called when inactives is accessed, and lock is held then.
|
||||
@@ -485,13 +485,13 @@ public class PeerGroup implements TransactionBroadcaster {
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch for enabling download of pending transaction dependencies. A change of value only takes effect for newly
|
||||
* connected peers.
|
||||
* Configure download of pending transaction dependencies. A change of values only takes effect for newly connected
|
||||
* peers.
|
||||
*/
|
||||
public void setDownloadTxDependencies(boolean downloadTxDependencies) {
|
||||
public void setDownloadTxDependencies(int depth) {
|
||||
lock.lock();
|
||||
try {
|
||||
this.downloadTxDependencies = downloadTxDependencies;
|
||||
this.downloadTxDependencyDepth = depth;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
@@ -1511,7 +1511,7 @@ public class PeerGroup implements TransactionBroadcaster {
|
||||
/** You can override this to customise the creation of {@link Peer} objects. */
|
||||
@GuardedBy("lock")
|
||||
protected Peer createPeer(PeerAddress address, VersionMessage ver) {
|
||||
return new Peer(params, ver, address, chain, downloadTxDependencies);
|
||||
return new Peer(params, ver, address, chain, downloadTxDependencyDepth);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -554,8 +554,7 @@ public class PeerTest extends TestWithNetworkConnections {
|
||||
|
||||
@Test
|
||||
public void recursiveDependencyDownload() throws Exception {
|
||||
// Using ping or notfound?
|
||||
connectWithVersion(70001, VersionMessage.NODE_NETWORK);
|
||||
connect();
|
||||
// Check that we can download all dependencies of an unconfirmed relevant transaction from the mempool.
|
||||
ECKey to = new ECKey();
|
||||
|
||||
@@ -574,9 +573,9 @@ public class PeerTest extends TestWithNetworkConnections {
|
||||
// -> [t8]
|
||||
// The ones in brackets are assumed to be in the chain and are represented only by hashes.
|
||||
Transaction t2 = FakeTxBuilder.createFakeTx(params, COIN, to);
|
||||
Sha256Hash t5 = t2.getInput(0).getOutpoint().getHash();
|
||||
Sha256Hash t5hash = t2.getInput(0).getOutpoint().getHash();
|
||||
Transaction t4 = FakeTxBuilder.createFakeTx(params, COIN, new ECKey());
|
||||
Sha256Hash t6 = t4.getInput(0).getOutpoint().getHash();
|
||||
Sha256Hash t6hash = t4.getInput(0).getOutpoint().getHash();
|
||||
t4.addOutput(COIN, new ECKey());
|
||||
Transaction t3 = new Transaction(params);
|
||||
t3.addInput(t4.getOutput(0));
|
||||
@@ -584,10 +583,10 @@ public class PeerTest extends TestWithNetworkConnections {
|
||||
Transaction t1 = new Transaction(params);
|
||||
t1.addInput(t2.getOutput(0));
|
||||
t1.addInput(t3.getOutput(0));
|
||||
Sha256Hash someHash = Sha256Hash.wrap("2b801dd82f01d17bbde881687bf72bc62e2faa8ab8133d36fcb8c3abe7459da6");
|
||||
t1.addInput(new TransactionInput(params, t1, new byte[]{}, new TransactionOutPoint(params, 0, someHash)));
|
||||
Sha256Hash anotherHash = Sha256Hash.wrap("3b801dd82f01d17bbde881687bf72bc62e2faa8ab8133d36fcb8c3abe7459da6");
|
||||
t1.addInput(new TransactionInput(params, t1, new byte[]{}, new TransactionOutPoint(params, 1, anotherHash)));
|
||||
Sha256Hash t7hash = Sha256Hash.wrap("2b801dd82f01d17bbde881687bf72bc62e2faa8ab8133d36fcb8c3abe7459da6");
|
||||
t1.addInput(new TransactionInput(params, t1, new byte[]{}, new TransactionOutPoint(params, 0, t7hash)));
|
||||
Sha256Hash t8hash = Sha256Hash.wrap("3b801dd82f01d17bbde881687bf72bc62e2faa8ab8133d36fcb8c3abe7459da6");
|
||||
t1.addInput(new TransactionInput(params, t1, new byte[]{}, new TransactionOutPoint(params, 1, t8hash)));
|
||||
t1.addOutput(COIN, to);
|
||||
t1 = FakeTxBuilder.roundTripTransaction(params, t1);
|
||||
t2 = FakeTxBuilder.roundTripTransaction(params, t2);
|
||||
@@ -607,19 +606,19 @@ public class PeerTest extends TestWithNetworkConnections {
|
||||
// We want its dependencies so ask for them.
|
||||
ListenableFuture<List<Transaction>> futures = peer.downloadDependencies(t1);
|
||||
assertFalse(futures.isDone());
|
||||
// It will recursively ask for the dependencies of t1: t2, t3, someHash and anotherHash.
|
||||
// It will recursively ask for the dependencies of t1: t2, t3, t7, t8.
|
||||
getdata = (GetDataMessage) outbound(writeTarget);
|
||||
assertEquals(4, getdata.getItems().size());
|
||||
assertEquals(t2.getHash(), getdata.getItems().get(0).hash);
|
||||
assertEquals(t3.getHash(), getdata.getItems().get(1).hash);
|
||||
assertEquals(someHash, getdata.getItems().get(2).hash);
|
||||
assertEquals(anotherHash, getdata.getItems().get(3).hash);
|
||||
assertEquals(t7hash, getdata.getItems().get(2).hash);
|
||||
assertEquals(t8hash, getdata.getItems().get(3).hash);
|
||||
// Deliver the requested transactions.
|
||||
inbound(writeTarget, t2);
|
||||
inbound(writeTarget, t3);
|
||||
NotFoundMessage notFound = new NotFoundMessage(params);
|
||||
notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, someHash));
|
||||
notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, anotherHash));
|
||||
notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, t7hash));
|
||||
notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, t8hash));
|
||||
inbound(writeTarget, notFound);
|
||||
assertFalse(futures.isDone());
|
||||
// It will recursively ask for the dependencies of t2: t5 and t4, but not t3 because it already found t4.
|
||||
@@ -627,7 +626,7 @@ public class PeerTest extends TestWithNetworkConnections {
|
||||
assertEquals(getdata.getItems().get(0).hash, t2.getInput(0).getOutpoint().getHash());
|
||||
// t5 isn't found and t4 is.
|
||||
notFound = new NotFoundMessage(params);
|
||||
notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, t5));
|
||||
notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, t5hash));
|
||||
inbound(writeTarget, notFound);
|
||||
assertFalse(futures.isDone());
|
||||
// Request t4 ...
|
||||
@@ -636,19 +635,71 @@ public class PeerTest extends TestWithNetworkConnections {
|
||||
inbound(writeTarget, t4);
|
||||
// Continue to explore the t4 branch and ask for t6, which is in the chain.
|
||||
getdata = (GetDataMessage) outbound(writeTarget);
|
||||
assertEquals(t6, getdata.getItems().get(0).hash);
|
||||
assertEquals(t6hash, getdata.getItems().get(0).hash);
|
||||
notFound = new NotFoundMessage(params);
|
||||
notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, t6));
|
||||
notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, t6hash));
|
||||
inbound(writeTarget, notFound);
|
||||
pingAndWait(writeTarget);
|
||||
// That's it, we explored the entire tree.
|
||||
assertTrue(futures.isDone());
|
||||
List<Transaction> results = futures.get();
|
||||
assertEquals(3, results.size());
|
||||
assertTrue(results.contains(t2));
|
||||
assertTrue(results.contains(t3));
|
||||
assertTrue(results.contains(t4));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void recursiveDependencyDownload_depthLimited() throws Exception {
|
||||
peer.setDownloadTxDependencies(1); // Depth limit
|
||||
connect();
|
||||
|
||||
// Make some fake transactions in the following graph:
|
||||
// t1 -> t2 -> t3 -> [t4]
|
||||
// The ones in brackets are assumed to be in the chain and are represented only by hashes.
|
||||
Sha256Hash t4hash = Sha256Hash.wrap("2b801dd82f01d17bbde881687bf72bc62e2faa8ab8133d36fcb8c3abe7459da6");
|
||||
Transaction t3 = new Transaction(params);
|
||||
t3.addInput(new TransactionInput(params, t3, new byte[]{}, new TransactionOutPoint(params, 0, t4hash)));
|
||||
t3.addOutput(COIN, new ECKey());
|
||||
t3 = FakeTxBuilder.roundTripTransaction(params, t3);
|
||||
Transaction t2 = new Transaction(params);
|
||||
t2.addInput(t3.getOutput(0));
|
||||
t2.addOutput(COIN, new ECKey());
|
||||
t2 = FakeTxBuilder.roundTripTransaction(params, t2);
|
||||
Transaction t1 = new Transaction(params);
|
||||
t1.addInput(t2.getOutput(0));
|
||||
t1.addOutput(COIN, new ECKey());
|
||||
t1 = FakeTxBuilder.roundTripTransaction(params, t1);
|
||||
|
||||
// Announce the first one. Wait for it to be downloaded.
|
||||
InventoryMessage inv = new InventoryMessage(params);
|
||||
inv.addTransaction(t1);
|
||||
inbound(writeTarget, inv);
|
||||
GetDataMessage getdata = (GetDataMessage) outbound(writeTarget);
|
||||
Threading.waitForUserCode();
|
||||
assertEquals(t1.getHash(), getdata.getItems().get(0).hash);
|
||||
inbound(writeTarget, t1);
|
||||
pingAndWait(writeTarget);
|
||||
// We want its dependencies so ask for them.
|
||||
ListenableFuture<List<Transaction>> futures = peer.downloadDependencies(t1);
|
||||
assertFalse(futures.isDone());
|
||||
// level 1
|
||||
getdata = (GetDataMessage) outbound(writeTarget);
|
||||
assertEquals(1, getdata.getItems().size());
|
||||
assertEquals(t2.getHash(), getdata.getItems().get(0).hash);
|
||||
inbound(writeTarget, t2);
|
||||
// no level 2
|
||||
getdata = (GetDataMessage) outbound(writeTarget);
|
||||
assertNull(getdata);
|
||||
|
||||
// That's it, now double check what we've got
|
||||
pingAndWait(writeTarget);
|
||||
assertTrue(futures.isDone());
|
||||
List<Transaction> results = futures.get();
|
||||
assertEquals(1, results.size());
|
||||
assertTrue(results.contains(t2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void timeLockedTransactionNew() throws Exception {
|
||||
connectWithVersion(70001, VersionMessage.NODE_NETWORK);
|
||||
|
||||
Reference in New Issue
Block a user