-
Notifications
You must be signed in to change notification settings - Fork 839
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix to avoid broadcasting full blob txs #6835
Changes from all commits
14a6c64
221bcf8
c3d96e8
09bd00f
2cd91ef
a51ce2e
358d4c7
6e852f2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,8 +30,10 @@ | |
import java.util.EnumSet; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Random; | ||
import java.util.stream.Collectors; | ||
|
||
import com.google.common.annotations.VisibleForTesting; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
|
@@ -47,16 +49,33 @@ public class TransactionBroadcaster implements TransactionBatchAddedListener { | |
private final TransactionsMessageSender transactionsMessageSender; | ||
private final NewPooledTransactionHashesMessageSender newPooledTransactionHashesMessageSender; | ||
private final EthContext ethContext; | ||
private final Random random; | ||
|
||
public TransactionBroadcaster( | ||
final EthContext ethContext, | ||
final PeerTransactionTracker transactionTracker, | ||
final TransactionsMessageSender transactionsMessageSender, | ||
final NewPooledTransactionHashesMessageSender newPooledTransactionHashesMessageSender) { | ||
this( | ||
ethContext, | ||
transactionTracker, | ||
transactionsMessageSender, | ||
newPooledTransactionHashesMessageSender, | ||
null); | ||
} | ||
|
||
@VisibleForTesting | ||
protected TransactionBroadcaster( | ||
final EthContext ethContext, | ||
final PeerTransactionTracker transactionTracker, | ||
final TransactionsMessageSender transactionsMessageSender, | ||
final NewPooledTransactionHashesMessageSender newPooledTransactionHashesMessageSender, | ||
final Long seed) { | ||
this.transactionTracker = transactionTracker; | ||
this.transactionsMessageSender = transactionsMessageSender; | ||
this.newPooledTransactionHashesMessageSender = newPooledTransactionHashesMessageSender; | ||
this.ethContext = ethContext; | ||
this.random = seed != null ? new Random(seed) : new Random(); | ||
} | ||
|
||
public void relayTransactionPoolTo( | ||
|
@@ -65,7 +84,13 @@ public void relayTransactionPoolTo( | |
if (peer.hasSupportForMessage(EthPV65.NEW_POOLED_TRANSACTION_HASHES)) { | ||
sendTransactionHashes(toTransactionList(pendingTransactions), List.of(peer)); | ||
} else { | ||
sendFullTransactions(toTransactionList(pendingTransactions), List.of(peer)); | ||
// we need to exclude txs that support hash only broadcasting | ||
final var fullBroadcastTxs = | ||
pendingTransactions.stream() | ||
.map(PendingTransaction::getTransaction) | ||
.filter(tx -> !ANNOUNCE_HASH_ONLY_TX_TYPES.contains(tx.getType())) | ||
.toList(); | ||
sendFullTransactions(fullBroadcastTxs, List.of(peer)); | ||
} | ||
} | ||
} | ||
|
@@ -77,7 +102,7 @@ public void onTransactionsAdded(final Collection<Transaction> transactions) { | |
return; | ||
} | ||
|
||
final int numPeersToSendFullTransactions = (int) Math.ceil(Math.sqrt(currPeerCount)); | ||
final int numPeersToSendFullTransactions = (int) Math.round(Math.sqrt(currPeerCount)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This allows for rounding down as well as up, instead of ceil which is up only. It still works, but I'm unclear why it is necessary or preferred? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it is not necessary, just seems more appropriate to round instead of ceil, to avoid strange results when few peers are connected, for example with 2 peers, with round we have 1, otherwise is 2. |
||
|
||
final Map<Boolean, List<Transaction>> transactionByBroadcastMode = | ||
transactions.stream() | ||
|
@@ -107,7 +132,7 @@ public void onTransactionsAdded(final Collection<Transaction> transactions) { | |
numPeersToSendFullTransactions - sendOnlyFullTransactionPeers.size(), | ||
sendOnlyHashPeers.size()); | ||
|
||
Collections.shuffle(sendOnlyHashPeers); | ||
Collections.shuffle(sendOnlyHashPeers, random); | ||
|
||
// move peers from the mixed list to reach the required size for full transaction peers | ||
movePeersBetweenLists(sendOnlyHashPeers, sendMixedPeers, delta); | ||
|
@@ -121,7 +146,7 @@ public void onTransactionsAdded(final Collection<Transaction> transactions) { | |
.addArgument(sendOnlyHashPeers::size) | ||
.addArgument(sendMixedPeers::size) | ||
.addArgument(sendOnlyFullTransactionPeers) | ||
.addArgument(() -> sendOnlyHashPeers.toString() + sendMixedPeers.toString()) | ||
.addArgument(() -> sendOnlyHashPeers.toString() + sendMixedPeers) | ||
.log(); | ||
|
||
sendToFullTransactionsPeers( | ||
|
@@ -141,7 +166,7 @@ private void sendToOnlyHashPeers( | |
final Map<Boolean, List<Transaction>> txsByHashOnlyBroadcast, | ||
final List<EthPeer> hashOnlyPeers) { | ||
final List<Transaction> allTransactions = | ||
txsByHashOnlyBroadcast.values().stream().flatMap(List::stream).collect(Collectors.toList()); | ||
txsByHashOnlyBroadcast.values().stream().flatMap(List::stream).toList(); | ||
|
||
sendTransactionHashes(allTransactions, hashOnlyPeers); | ||
} | ||
|
@@ -175,7 +200,7 @@ private void sendTransactionHashes( | |
.forEach( | ||
peer -> { | ||
transactions.forEach( | ||
transaction -> transactionTracker.addToPeerSendQueue(peer, transaction)); | ||
transaction -> transactionTracker.addToPeerHashSendQueue(peer, transaction)); | ||
ethContext | ||
.getScheduler() | ||
.scheduleSyncWorkerTask( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -55,7 +55,7 @@ | |
@ExtendWith(MockitoExtension.class) | ||
@MockitoSettings(strictness = Strictness.LENIENT) | ||
public class TransactionBroadcasterTest { | ||
|
||
private static final Long FIXED_RANDOM_SEED = 0L; | ||
@Mock private EthContext ethContext; | ||
@Mock private EthPeers ethPeers; | ||
@Mock private EthScheduler ethScheduler; | ||
|
@@ -92,12 +92,14 @@ public void setUp() { | |
when(ethContext.getEthPeers()).thenReturn(ethPeers); | ||
when(ethContext.getScheduler()).thenReturn(ethScheduler); | ||
|
||
// we use the fixed random seed to have a predictable shuffle of peers | ||
txBroadcaster = | ||
new TransactionBroadcaster( | ||
ethContext, | ||
transactionTracker, | ||
transactionsMessageSender, | ||
newPooledTransactionHashesMessageSender); | ||
newPooledTransactionHashesMessageSender, | ||
FIXED_RANDOM_SEED); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Excellent move. My list of "flaky tests to fix" thanks you! |
||
} | ||
|
||
@Test | ||
|
@@ -132,7 +134,7 @@ public void relayTransactionHashesFromPoolWhenPeerSupportEth65() { | |
|
||
txBroadcaster.relayTransactionPoolTo(ethPeerWithEth65, pendingTxs); | ||
|
||
verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65, txs); | ||
verifyTransactionAddedToPeerHashSendingQueue(ethPeerWithEth65, txs); | ||
|
||
sendTaskCapture.getValue().run(); | ||
|
||
|
@@ -177,14 +179,16 @@ public void onTransactionsAddedWithOnlyFewEth65PeersSendFullTransactions() { | |
List<Transaction> txs = toTransactionList(setupTransactionPool(1, 1)); | ||
|
||
txBroadcaster.onTransactionsAdded(txs); | ||
|
||
verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65, txs); | ||
// the shuffled hash only peer list is always: | ||
// [ethPeerWithEth65_3, ethPeerWithEth65_2, ethPeerWithEth65] | ||
// so ethPeerWithEth65 and ethPeerWithEth65_2 are moved to the mixed broadcast list | ||
verifyTransactionAddedToPeerHashSendingQueue(ethPeerWithEth65, txs); | ||
verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65_2, txs); | ||
|
||
sendTaskCapture.getAllValues().forEach(Runnable::run); | ||
|
||
verify(transactionsMessageSender, times(2)).sendTransactionsToPeer(any(EthPeer.class)); | ||
verifyNoInteractions(newPooledTransactionHashesMessageSender); | ||
verify(transactionsMessageSender).sendTransactionsToPeer(ethPeerWithEth65_2); | ||
verify(newPooledTransactionHashesMessageSender).sendTransactionHashesToPeer(ethPeerWithEth65); | ||
} | ||
|
||
@Test | ||
|
@@ -196,10 +200,12 @@ public void onTransactionsAddedWithOnlyEth65PeersSendFullTransactionsAndTransact | |
List<Transaction> txs = toTransactionList(setupTransactionPool(1, 1)); | ||
|
||
txBroadcaster.onTransactionsAdded(txs); | ||
|
||
// the shuffled hash only peer list is always: | ||
// [ethPeerWithEth65_3, ethPeerWithEth65_2, ethPeerWithEth65] | ||
// so ethPeerWithEth65 and ethPeerWithEth65_2 are moved to the mixed broadcast list | ||
verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65, txs); | ||
verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65_2, txs); | ||
verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65_3, txs); | ||
verifyTransactionAddedToPeerHashSendingQueue(ethPeerWithEth65_3, txs); | ||
|
||
sendTaskCapture.getAllValues().forEach(Runnable::run); | ||
|
||
|
@@ -218,8 +224,10 @@ public void onTransactionsAddedWithMixedPeersSendFullTransactionsAndTransactionH | |
List<Transaction> txs = toTransactionList(setupTransactionPool(1, 1)); | ||
|
||
txBroadcaster.onTransactionsAdded(txs); | ||
|
||
verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65, txs); | ||
// the shuffled hash only peer list is always: | ||
// [ethPeerWithEth65, ethPeerWithEth65_2] | ||
// so ethPeerWithEth65_2 is moved to the mixed broadcast list | ||
verifyTransactionAddedToPeerHashSendingQueue(ethPeerWithEth65, txs); | ||
verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65_2, txs); | ||
verifyTransactionAddedToPeerSendingQueue(ethPeerNoEth65, txs); | ||
|
||
|
@@ -250,9 +258,11 @@ public void onTransactionsAddedWithMixedPeersSendFullTransactionsAndTransactionH | |
List<Transaction> txs = toTransactionList(setupTransactionPool(BLOB, 0, 1)); | ||
|
||
txBroadcaster.onTransactionsAdded(txs); | ||
|
||
verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65, txs); | ||
verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65_2, txs); | ||
// the shuffled hash only peer list is always: | ||
// [ethPeerWithEth65, ethPeerWithEth65_2] | ||
// so ethPeerWithEth65_2 is moved to the mixed broadcast list | ||
verifyTransactionAddedToPeerHashSendingQueue(ethPeerWithEth65, txs); | ||
verifyTransactionAddedToPeerHashSendingQueue(ethPeerWithEth65_2, txs); | ||
verifyNoTransactionAddedToPeerSendingQueue(ethPeerNoEth65); | ||
|
||
sendTaskCapture.getAllValues().forEach(Runnable::run); | ||
|
@@ -268,7 +278,6 @@ public void onTransactionsAddedWithMixedPeersSendFullTransactionsAndTransactionH | |
|
||
@Test | ||
public void onTransactionsAddedWithMixedPeersAndMixedBroadcastKind() { | ||
|
||
List<EthPeer> eth65Peers = List.of(ethPeerWithEth65, ethPeerWithEth65_2); | ||
|
||
when(ethPeers.peerCount()).thenReturn(3); | ||
|
@@ -285,9 +294,12 @@ public void onTransactionsAddedWithMixedPeersAndMixedBroadcastKind() { | |
mixedTxs.addAll(hashBroadcastTxs); | ||
|
||
txBroadcaster.onTransactionsAdded(mixedTxs); | ||
|
||
verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65, mixedTxs); | ||
verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65_2, mixedTxs); | ||
// the shuffled hash only peer list is always: | ||
// [ethPeerWithEth65, ethPeerWithEth65_2] | ||
// so ethPeerWithEth65_2 is moved to the mixed broadcast list | ||
verifyTransactionAddedToPeerHashSendingQueue(ethPeerWithEth65, mixedTxs); | ||
verifyTransactionAddedToPeerHashSendingQueue(ethPeerWithEth65_2, hashBroadcastTxs); | ||
verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65_2, fullBroadcastTxs); | ||
verifyTransactionAddedToPeerSendingQueue(ethPeerNoEth65, fullBroadcastTxs); | ||
|
||
sendTaskCapture.getAllValues().forEach(Runnable::run); | ||
|
@@ -348,6 +360,16 @@ private void verifyTransactionAddedToPeerSendingQueue( | |
.containsExactlyInAnyOrderElementsOf(transactions); | ||
} | ||
|
||
private void verifyTransactionAddedToPeerHashSendingQueue( | ||
final EthPeer peer, final Collection<Transaction> transactions) { | ||
|
||
ArgumentCaptor<Transaction> trackedTransactions = ArgumentCaptor.forClass(Transaction.class); | ||
verify(transactionTracker, times(transactions.size())) | ||
.addToPeerHashSendQueue(eq(peer), trackedTransactions.capture()); | ||
assertThat(trackedTransactions.getAllValues()) | ||
.containsExactlyInAnyOrderElementsOf(transactions); | ||
} | ||
|
||
private void verifyNoTransactionAddedToPeerSendingQueue(final EthPeer peer) { | ||
|
||
verify(transactionTracker, times(0)).addToPeerSendQueue(eq(peer), any()); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should work, but i wonder if it would be more correct to mark them as seen after we know they've been sent to the peer. we could restore them to the cache in the exception handling of NewPooledTransactionHashesMessageSender:63
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point, will review next