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

Resolve some race conditions.

It was possible that during saving of a wallet a network thread would update the confidence metrics due to broadcast announcements. This change makes TransactionConfidence use a COW list so the broadcast peer set can be iterated over safely. Resolves issue 277.
This commit is contained in:
Mike Hearn 2013-01-12 14:11:11 +01:00
parent 9a65d4cab8
commit 916e33254f
4 changed files with 33 additions and 27 deletions

View File

@ -996,7 +996,7 @@ public class PeerGroup extends AbstractIdleService {
tx.getConfidence().addEventListener(new TransactionConfidence.Listener() { tx.getConfidence().addEventListener(new TransactionConfidence.Listener() {
public void onConfidenceChanged(Transaction tx) { public void onConfidenceChanged(Transaction tx) {
// This will run in a peer thread. // This will run in a peer thread.
final int numSeenPeers = tx.getConfidence().getBroadcastBy().size(); final int numSeenPeers = tx.getConfidence().numBroadcastPeers();
boolean done = false; boolean done = false;
log.info("broadcastTransaction: TX {} seen by {} peers", pinnedTx.getHashAsString(), log.info("broadcastTransaction: TX {} seen by {} peers", pinnedTx.getHashAsString(),
numSeenPeers); numSeenPeers);

View File

@ -22,8 +22,8 @@ import com.google.common.base.Preconditions;
import java.io.Serializable; import java.io.Serializable;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.ListIterator;
import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList;
/** /**
* <p>A TransactionConfidence object tracks data you can use to make a confidence decision about a transaction. * <p>A TransactionConfidence object tracks data you can use to make a confidence decision about a transaction.
@ -61,7 +61,7 @@ public class TransactionConfidence implements Serializable {
* IP address as an approximation. It's obviously vulnerable to being gamed if we allow arbitrary people to connect * IP address as an approximation. It's obviously vulnerable to being gamed if we allow arbitrary people to connect
* to us, so only peers we explicitly connected to should go here. * to us, so only peers we explicitly connected to should go here.
*/ */
private Set<PeerAddress> broadcastBy; private CopyOnWriteArrayList<PeerAddress> broadcastBy;
/** The Transaction that this confidence object is associated with. */ /** The Transaction that this confidence object is associated with. */
private Transaction transaction; private Transaction transaction;
// Lazily created listeners array. // Lazily created listeners array.
@ -180,7 +180,7 @@ public class TransactionConfidence implements Serializable {
public TransactionConfidence(Transaction tx) { public TransactionConfidence(Transaction tx) {
// Assume a default number of peers for our set. // Assume a default number of peers for our set.
broadcastBy = new HashSet<PeerAddress>(10); broadcastBy = new CopyOnWriteArrayList<PeerAddress>();
transaction = tx; transaction = tx;
} }
@ -234,8 +234,9 @@ public class TransactionConfidence implements Serializable {
* @param address IP address of the peer, used as a proxy for identity. * @param address IP address of the peer, used as a proxy for identity.
*/ */
public void markBroadcastBy(PeerAddress address) { public void markBroadcastBy(PeerAddress address) {
if (!broadcastBy.addIfAbsent(address))
return; // Duplicate.
synchronized (this) { synchronized (this) {
broadcastBy.add(address);
if (getConfidenceType() == ConfidenceType.UNKNOWN) { if (getConfidenceType() == ConfidenceType.UNKNOWN) {
this.confidenceType = ConfidenceType.NOT_SEEN_IN_CHAIN; this.confidenceType = ConfidenceType.NOT_SEEN_IN_CHAIN;
} }
@ -246,15 +247,20 @@ public class TransactionConfidence implements Serializable {
/** /**
* Returns how many peers have been passed to {@link TransactionConfidence#markBroadcastBy}. * Returns how many peers have been passed to {@link TransactionConfidence#markBroadcastBy}.
*/ */
public synchronized int numBroadcastPeers() { public int numBroadcastPeers() {
return broadcastBy.size(); return broadcastBy.size();
} }
/** /**
* Returns a synchronized set of {@link PeerAddress}es that announced the transaction. * Returns a synchronized set of {@link PeerAddress}es that announced the transaction.
*/ */
public synchronized Set<PeerAddress> getBroadcastBy() { public ListIterator<PeerAddress> getBroadcastBy() {
return broadcastBy; return broadcastBy.listIterator();
}
/** Returns true if the given address has been seen via markBroadcastBy() */
public boolean wasBroadcastBy(PeerAddress address) {
return broadcastBy.contains(address);
} }
@Override @Override

View File

@ -31,10 +31,7 @@ import java.io.OutputStream;
import java.math.BigInteger; import java.math.BigInteger;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.Collection; import java.util.*;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/** /**
* Serialize and de-serialize a wallet to a byte stream containing a * Serialize and de-serialize a wallet to a byte stream containing a
@ -203,6 +200,7 @@ public class WalletProtobufSerializer {
private static void writeConfidence(Protos.Transaction.Builder txBuilder, private static void writeConfidence(Protos.Transaction.Builder txBuilder,
TransactionConfidence confidence, TransactionConfidence confidence,
Protos.TransactionConfidence.Builder confidenceBuilder) { Protos.TransactionConfidence.Builder confidenceBuilder) {
synchronized (confidence) {
confidenceBuilder.setType(Protos.TransactionConfidence.Type.valueOf(confidence.getConfidenceType().getValue())); confidenceBuilder.setType(Protos.TransactionConfidence.Type.valueOf(confidence.getConfidenceType().getValue()));
if (confidence.getConfidenceType() == ConfidenceType.BUILDING) { if (confidence.getConfidenceType() == ConfidenceType.BUILDING) {
confidenceBuilder.setAppearedAtHeight(confidence.getAppearedAtChainHeight()); confidenceBuilder.setAppearedAtHeight(confidence.getAppearedAtChainHeight());
@ -215,7 +213,9 @@ public class WalletProtobufSerializer {
Sha256Hash overridingHash = confidence.getOverridingTransaction().getHash(); Sha256Hash overridingHash = confidence.getOverridingTransaction().getHash();
confidenceBuilder.setOverridingTransaction(hashToByteString(overridingHash)); confidenceBuilder.setOverridingTransaction(hashToByteString(overridingHash));
} }
for (PeerAddress address : confidence.getBroadcastBy()) { }
for (ListIterator<PeerAddress> it = confidence.getBroadcastBy(); it.hasNext();) {
PeerAddress address = it.next();
Protos.PeerAddress proto = Protos.PeerAddress.newBuilder() Protos.PeerAddress proto = Protos.PeerAddress.newBuilder()
.setIpAddress(ByteString.copyFrom(address.getAddr().getAddress())) .setIpAddress(ByteString.copyFrom(address.getAddr().getAddress()))
.setPort(address.getPort()) .setPort(address.getPort())

View File

@ -268,8 +268,8 @@ public class PeerGroupTest extends TestWithNetworkConnections {
inbound(p1, tx); // returns nothing after a queue drain. inbound(p1, tx); // returns nothing after a queue drain.
// Two peers saw this tx hash. // Two peers saw this tx hash.
assertEquals(2, tx.getConfidence().numBroadcastPeers()); assertEquals(2, tx.getConfidence().numBroadcastPeers());
assertTrue(tx.getConfidence().getBroadcastBy().contains(peerOf(p1).getAddress())); assertTrue(tx.getConfidence().wasBroadcastBy(peerOf(p1).getAddress()));
assertTrue(tx.getConfidence().getBroadcastBy().contains(peerOf(p2).getAddress())); assertTrue(tx.getConfidence().wasBroadcastBy(peerOf(p2).getAddress()));
tx.getConfidence().addEventListener(new TransactionConfidence.Listener() { tx.getConfidence().addEventListener(new TransactionConfidence.Listener() {
public void onConfidenceChanged(Transaction tx) { public void onConfidenceChanged(Transaction tx) {
@ -280,7 +280,7 @@ public class PeerGroupTest extends TestWithNetworkConnections {
inbound(p3, inv); inbound(p3, inv);
assertEquals(tx, event[1]); assertEquals(tx, event[1]);
assertEquals(3, tx.getConfidence().numBroadcastPeers()); assertEquals(3, tx.getConfidence().numBroadcastPeers());
assertTrue(tx.getConfidence().getBroadcastBy().contains(peerOf(p3).getAddress())); assertTrue(tx.getConfidence().wasBroadcastBy(peerOf(p3).getAddress()));
} }
@Test @Test