3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-11 17:55:53 +00:00

TransactionBroadcast: only consider a tx rejected if it has more than half peers signalling a reject.

This commit is contained in:
Mike Hearn 2015-01-29 19:24:02 +01:00
parent c981555be4
commit bc60f0d1f2
2 changed files with 42 additions and 14 deletions

View File

@ -16,18 +16,14 @@
package org.bitcoinj.core;
import org.bitcoinj.utils.Threading;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.*;
import com.google.common.base.*;
import com.google.common.util.concurrent.*;
import org.bitcoinj.utils.*;
import org.slf4j.*;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import javax.annotation.*;
import java.util.*;
/**
* Represents a single transaction broadcast that we are performing. A broadcast occurs after a new transaction is created
@ -49,8 +45,12 @@ public class TransactionBroadcast {
/** Used for shuffling the peers before broadcast: unit tests can replace this to make themselves deterministic. */
@VisibleForTesting
public static Random random = new Random();
private Transaction pinnedTx;
// Tracks which nodes sent us a reject message about this broadcast, if any. Useful for debugging.
private Map<Peer, RejectMessage> rejects = Collections.synchronizedMap(new HashMap<Peer, RejectMessage>());
// TODO: Context being owned by BlockChain isn't right w.r.t future intentions so it shouldn't really be optional here.
TransactionBroadcast(PeerGroup peerGroup, @Nullable Context context, Transaction tx) {
this.peerGroup = peerGroup;
@ -73,8 +73,14 @@ public class TransactionBroadcast {
if (m instanceof RejectMessage) {
RejectMessage rejectMessage = (RejectMessage)m;
if (tx.getHash().equals(rejectMessage.getRejectedObjectHash())) {
future.setException(new RejectedTransactionException(tx, rejectMessage));
peerGroup.removeEventListener(this);
rejects.put(peer, rejectMessage);
int size = rejects.size();
long threshold = Math.round(numWaitingFor / 2.0);
if (size > threshold) {
log.warn("Threshold for considering broadcast rejected has been reached ({}/{})", size, threshold);
future.setException(new RejectedTransactionException(tx, rejectMessage));
peerGroup.removeEventListener(this);
}
}
}
return m;
@ -147,7 +153,7 @@ public class TransactionBroadcast {
@Override
public void onConfidenceChanged(TransactionConfidence conf, ChangeReason reason) {
// The number of peers that announced this tx has gone up.
int numSeenPeers = conf.numBroadcastPeers();
int numSeenPeers = conf.numBroadcastPeers() + rejects.size();
boolean mined = tx.getAppearsInHashes() != null;
log.info("broadcastTransaction: {}: TX {} seen by {} peers{}", reason, pinnedTx.getHashAsString(),
numSeenPeers, mined ? " and mined" : "");

View File

@ -32,6 +32,7 @@ import org.junit.runners.Parameterized;
import java.util.Arrays;
import java.util.Collection;
import java.util.Random;
import java.util.concurrent.*;
import static org.bitcoinj.core.Coin.*;
import static com.google.common.base.Preconditions.checkNotNull;
@ -97,6 +98,27 @@ public class TransactionBroadcastTest extends TestWithPeerGroup {
assertTrue(future.isDone());
}
@Test
public void rejectHandling() throws Exception {
InboundMessageQueuer[] channels = { connectPeer(0), connectPeer(1), connectPeer(2), connectPeer(3), connectPeer(4) };
Transaction tx = new Transaction(params);
TransactionBroadcast broadcast = new TransactionBroadcast(peerGroup, blockChain.getContext(), tx);
ListenableFuture<Transaction> future = broadcast.broadcast();
// 0 and 3 are randomly selected to receive the broadcast.
assertEquals(tx, outbound(channels[1]));
assertEquals(tx, outbound(channels[2]));
assertEquals(tx, outbound(channels[4]));
RejectMessage reject = new RejectMessage(params, RejectMessage.RejectCode.DUST, tx.getHash(), "tx", "dust");
inbound(channels[1], reject);
inbound(channels[4], reject);
try {
future.get();
fail();
} catch (ExecutionException e) {
assertEquals(RejectedTransactionException.class, e.getCause().getClass());
}
}
@Test
public void retryFailedBroadcast() throws Exception {
// If we create a spend, it's sent to a peer that swallows it, and the peergroup is removed/re-added then