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:
parent
c981555be4
commit
bc60f0d1f2
@ -16,18 +16,14 @@
|
|||||||
|
|
||||||
package org.bitcoinj.core;
|
package org.bitcoinj.core;
|
||||||
|
|
||||||
import org.bitcoinj.utils.Threading;
|
import com.google.common.annotations.*;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.base.*;
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.util.concurrent.*;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import org.bitcoinj.utils.*;
|
||||||
import com.google.common.util.concurrent.SettableFuture;
|
import org.slf4j.*;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.*;
|
||||||
import java.util.Collections;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a single transaction broadcast that we are performing. A broadcast occurs after a new transaction is created
|
* 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. */
|
/** Used for shuffling the peers before broadcast: unit tests can replace this to make themselves deterministic. */
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public static Random random = new Random();
|
public static Random random = new Random();
|
||||||
|
|
||||||
private Transaction pinnedTx;
|
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.
|
// 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) {
|
TransactionBroadcast(PeerGroup peerGroup, @Nullable Context context, Transaction tx) {
|
||||||
this.peerGroup = peerGroup;
|
this.peerGroup = peerGroup;
|
||||||
@ -73,8 +73,14 @@ public class TransactionBroadcast {
|
|||||||
if (m instanceof RejectMessage) {
|
if (m instanceof RejectMessage) {
|
||||||
RejectMessage rejectMessage = (RejectMessage)m;
|
RejectMessage rejectMessage = (RejectMessage)m;
|
||||||
if (tx.getHash().equals(rejectMessage.getRejectedObjectHash())) {
|
if (tx.getHash().equals(rejectMessage.getRejectedObjectHash())) {
|
||||||
future.setException(new RejectedTransactionException(tx, rejectMessage));
|
rejects.put(peer, rejectMessage);
|
||||||
peerGroup.removeEventListener(this);
|
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;
|
return m;
|
||||||
@ -147,7 +153,7 @@ public class TransactionBroadcast {
|
|||||||
@Override
|
@Override
|
||||||
public void onConfidenceChanged(TransactionConfidence conf, ChangeReason reason) {
|
public void onConfidenceChanged(TransactionConfidence conf, ChangeReason reason) {
|
||||||
// The number of peers that announced this tx has gone up.
|
// 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;
|
boolean mined = tx.getAppearsInHashes() != null;
|
||||||
log.info("broadcastTransaction: {}: TX {} seen by {} peers{}", reason, pinnedTx.getHashAsString(),
|
log.info("broadcastTransaction: {}: TX {} seen by {} peers{}", reason, pinnedTx.getHashAsString(),
|
||||||
numSeenPeers, mined ? " and mined" : "");
|
numSeenPeers, mined ? " and mined" : "");
|
||||||
|
@ -32,6 +32,7 @@ import org.junit.runners.Parameterized;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
import static org.bitcoinj.core.Coin.*;
|
import static org.bitcoinj.core.Coin.*;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
@ -97,6 +98,27 @@ public class TransactionBroadcastTest extends TestWithPeerGroup {
|
|||||||
assertTrue(future.isDone());
|
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
|
@Test
|
||||||
public void retryFailedBroadcast() throws Exception {
|
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
|
// If we create a spend, it's sent to a peer that swallows it, and the peergroup is removed/re-added then
|
||||||
|
Loading…
x
Reference in New Issue
Block a user