mirror of
https://github.com/vacp2p/linea-besu.git
synced 2026-01-08 20:47:59 -05:00
Fix ETH65 causes crashes issues (#1601)
This PR adds a waiting list for NewPooledTransactionHashesMessage in order to group several hashes into a single GetPooledTransactionsFromPeerTask Signed-off-by: Karim TAAM <karim.t2am@gmail.com> Co-authored-by: Ratan Rai Sur <ratan.r.sur@gmail.com>
This commit is contained in:
@@ -12,10 +12,10 @@
|
||||
* Ibft2 will discard any received messages targeting a chain height <= current head - this resolves some corner cases in system correctness directly following block import. [#1575](https://github.com/hyperledger/besu/pull/1575)
|
||||
* EvmTool now throws `UnsupportedForkException` when there is an unknown fork and is YOLOv2 compatible [\#1584](https://github.com/hyperledger/besu/pull/1584)
|
||||
* `eth_newFilter` now supports `blockHash` parameter as per the spec [\#1548](https://github.com/hyperledger/besu/issues/1540). (`blockhash` is also still supported.)
|
||||
* Fixed an issue that caused loss of peers and desynchronization when eth65 was enabled [\#1601](https://github.com/hyperledger/besu/pull/1601)
|
||||
|
||||
#### Previously identified known issues
|
||||
|
||||
- [Eth/65 loses peers](KNOWN_ISSUES.md#eth65-loses-peers)
|
||||
- [Fast sync when running Besu on cloud providers](KNOWN_ISSUES.md#fast-sync-when-running-besu-on-cloud-providers)
|
||||
- [Privacy users with private transactions created using v1.3.4 or earlier](KNOWN_ISSUES.md#privacy-users-with-private-transactions-created-using-v134-or-earlier)
|
||||
|
||||
|
||||
@@ -5,14 +5,6 @@ in the current release are provided in the [Changelog](CHANGELOG.md).
|
||||
|
||||
Known issues are open issues categorized as [Very High or High impact](https://wiki.hyperledger.org/display/BESU/Defect+Prioritisation+Policy).
|
||||
|
||||
## Eth/65 loses peers
|
||||
|
||||
From v1.4.4, `eth/65` is [disabled by default](https://github.com/hyperledger/besu/pull/741).
|
||||
|
||||
If enabled, peers will slowly drop off and eventually Besu will fall out of sync or stop syncing.
|
||||
|
||||
A fix for this issue is being actively worked on.
|
||||
|
||||
## Fast sync when running Besu on cloud providers
|
||||
|
||||
A known [RocksDB issue](https://github.com/facebook/rocksdb/issues/6435) causes fast sync to fail
|
||||
|
||||
@@ -156,7 +156,7 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner {
|
||||
.privacyParameters(node.getPrivacyParameters())
|
||||
.nodeKey(new NodeKey(new KeyPairSecurityModule(KeyPairUtil.loadKeyPair(dataDir))))
|
||||
.metricsSystem(metricsSystem)
|
||||
.transactionPoolConfiguration(TransactionPoolConfiguration.builder().build())
|
||||
.transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT)
|
||||
.ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig())
|
||||
.clock(Clock.systemUTC())
|
||||
.isRevertReasonEnabled(node.isRevertReasonEnabled())
|
||||
|
||||
@@ -146,6 +146,7 @@ import org.hyperledger.besu.services.StorageServiceImpl;
|
||||
import org.hyperledger.besu.util.NetworkUtility;
|
||||
import org.hyperledger.besu.util.PermissioningConfigurationValidator;
|
||||
import org.hyperledger.besu.util.number.Fraction;
|
||||
import org.hyperledger.besu.util.number.Percentage;
|
||||
import org.hyperledger.besu.util.number.PositiveNumber;
|
||||
|
||||
import java.io.File;
|
||||
@@ -2077,7 +2078,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
|
||||
.txPoolMaxSize(txPoolMaxSize)
|
||||
.pooledTransactionHashesSize(pooledTransactionHashesSize)
|
||||
.pendingTxRetentionPeriod(pendingTxRetentionPeriod)
|
||||
.priceBump(priceBump)
|
||||
.priceBump(Percentage.fromInt(priceBump))
|
||||
.txFeeCap(txFeeCap)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -16,17 +16,23 @@ package org.hyperledger.besu.cli.options.unstable;
|
||||
|
||||
import org.hyperledger.besu.cli.options.CLIOptions;
|
||||
import org.hyperledger.besu.cli.options.OptionParser;
|
||||
import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration;
|
||||
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import picocli.CommandLine;
|
||||
|
||||
public class TransactionPoolOptions implements CLIOptions<TransactionPoolConfiguration.Builder> {
|
||||
public class TransactionPoolOptions
|
||||
implements CLIOptions<ImmutableTransactionPoolConfiguration.Builder> {
|
||||
private static final String TX_MESSAGE_KEEP_ALIVE_SEC_FLAG =
|
||||
"--Xincoming-tx-messages-keep-alive-seconds";
|
||||
|
||||
private static final String ETH65_TX_ANNOUNCED_BUFFERING_PERIOD_FLAG =
|
||||
"--Xeth65-tx-announced-buffering-period-milliseconds";
|
||||
|
||||
@CommandLine.Option(
|
||||
names = {TX_MESSAGE_KEEP_ALIVE_SEC_FLAG},
|
||||
paramLabel = "<INTEGER>",
|
||||
@@ -37,6 +43,16 @@ public class TransactionPoolOptions implements CLIOptions<TransactionPoolConfigu
|
||||
private Integer txMessageKeepAliveSeconds =
|
||||
TransactionPoolConfiguration.DEFAULT_TX_MSG_KEEP_ALIVE;
|
||||
|
||||
@CommandLine.Option(
|
||||
names = {ETH65_TX_ANNOUNCED_BUFFERING_PERIOD_FLAG},
|
||||
paramLabel = "<LONG>",
|
||||
hidden = true,
|
||||
description =
|
||||
"The period for which the announced transactions remain in the buffer before being requested from the peers in milliseconds (default: ${DEFAULT-VALUE})",
|
||||
arity = "1")
|
||||
private long eth65TrxAnnouncedBufferingPeriod =
|
||||
TransactionPoolConfiguration.ETH65_TRX_ANNOUNCED_BUFFERING_PERIOD.toMillis();
|
||||
|
||||
private TransactionPoolOptions() {}
|
||||
|
||||
public static TransactionPoolOptions create() {
|
||||
@@ -46,18 +62,24 @@ public class TransactionPoolOptions implements CLIOptions<TransactionPoolConfigu
|
||||
public static TransactionPoolOptions fromConfig(final TransactionPoolConfiguration config) {
|
||||
final TransactionPoolOptions options = TransactionPoolOptions.create();
|
||||
options.txMessageKeepAliveSeconds = config.getTxMessageKeepAliveSeconds();
|
||||
options.eth65TrxAnnouncedBufferingPeriod =
|
||||
config.getEth65TrxAnnouncedBufferingPeriod().toMillis();
|
||||
return options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionPoolConfiguration.Builder toDomainObject() {
|
||||
return TransactionPoolConfiguration.builder()
|
||||
.txMessageKeepAliveSeconds(txMessageKeepAliveSeconds);
|
||||
public ImmutableTransactionPoolConfiguration.Builder toDomainObject() {
|
||||
return ImmutableTransactionPoolConfiguration.builder()
|
||||
.txMessageKeepAliveSeconds(txMessageKeepAliveSeconds)
|
||||
.eth65TrxAnnouncedBufferingPeriod(Duration.ofMillis(eth65TrxAnnouncedBufferingPeriod));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getCLIOptions() {
|
||||
return Arrays.asList(
|
||||
TX_MESSAGE_KEEP_ALIVE_SEC_FLAG, OptionParser.format(txMessageKeepAliveSeconds));
|
||||
TX_MESSAGE_KEEP_ALIVE_SEC_FLAG,
|
||||
OptionParser.format(txMessageKeepAliveSeconds),
|
||||
ETH65_TX_ANNOUNCED_BUFFERING_PERIOD_FLAG,
|
||||
OptionParser.format(eth65TrxAnnouncedBufferingPeriod));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ public class PrivacyReorgTest {
|
||||
.dataDirectory(dataDir)
|
||||
.clock(TestClock.fixed())
|
||||
.privacyParameters(privacyParameters)
|
||||
.transactionPoolConfiguration(TransactionPoolConfiguration.builder().build())
|
||||
.transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT)
|
||||
.gasLimitCalculator(GasLimitCalculator.constant())
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ public class PrivacyTest {
|
||||
.dataDirectory(dataDir)
|
||||
.clock(TestClock.fixed())
|
||||
.privacyParameters(privacyParameters)
|
||||
.transactionPoolConfiguration(TransactionPoolConfiguration.builder().build())
|
||||
.transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT)
|
||||
.gasLimitCalculator(GasLimitCalculator.constant())
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ public final class RunnerTest {
|
||||
.metricsSystem(noOpMetricsSystem)
|
||||
.privacyParameters(PrivacyParameters.DEFAULT)
|
||||
.clock(TestClock.fixed())
|
||||
.transactionPoolConfiguration(TransactionPoolConfiguration.builder().build())
|
||||
.transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT)
|
||||
.storageProvider(createKeyValueStorageProvider(dataDirAhead, dbAhead))
|
||||
.gasLimitCalculator(GasLimitCalculator.constant())
|
||||
.build()) {
|
||||
@@ -184,7 +184,7 @@ public final class RunnerTest {
|
||||
.metricsSystem(noOpMetricsSystem)
|
||||
.privacyParameters(PrivacyParameters.DEFAULT)
|
||||
.clock(TestClock.fixed())
|
||||
.transactionPoolConfiguration(TransactionPoolConfiguration.builder().build())
|
||||
.transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT)
|
||||
.storageProvider(createKeyValueStorageProvider(dataDirAhead, dbAhead))
|
||||
.gasLimitCalculator(GasLimitCalculator.constant())
|
||||
.build();
|
||||
@@ -248,7 +248,7 @@ public final class RunnerTest {
|
||||
.metricsSystem(noOpMetricsSystem)
|
||||
.privacyParameters(PrivacyParameters.DEFAULT)
|
||||
.clock(TestClock.fixed())
|
||||
.transactionPoolConfiguration(TransactionPoolConfiguration.builder().build())
|
||||
.transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT)
|
||||
.gasLimitCalculator(GasLimitCalculator.constant())
|
||||
.build();
|
||||
final EnodeURL enode = runnerAhead.getLocalEnode().get();
|
||||
|
||||
@@ -90,7 +90,7 @@ public final class RlpBlockExporterTest {
|
||||
.privacyParameters(PrivacyParameters.DEFAULT)
|
||||
.dataDirectory(dataDir)
|
||||
.clock(TestClock.fixed())
|
||||
.transactionPoolConfiguration(TransactionPoolConfiguration.builder().build())
|
||||
.transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT)
|
||||
.gasLimitCalculator(GasLimitCalculator.constant())
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -427,7 +427,7 @@ public abstract class JsonBlockImporterTest {
|
||||
.privacyParameters(PrivacyParameters.DEFAULT)
|
||||
.dataDirectory(dataDir)
|
||||
.clock(TestClock.fixed())
|
||||
.transactionPoolConfiguration(TransactionPoolConfiguration.builder().build())
|
||||
.transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT)
|
||||
.gasLimitCalculator(GasLimitCalculator.constant())
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public final class RlpBlockImporterTest {
|
||||
.privacyParameters(PrivacyParameters.DEFAULT)
|
||||
.dataDirectory(dataDir)
|
||||
.clock(TestClock.fixed())
|
||||
.transactionPoolConfiguration(TransactionPoolConfiguration.builder().build())
|
||||
.transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT)
|
||||
.gasLimitCalculator(GasLimitCalculator.constant())
|
||||
.build();
|
||||
final RlpBlockImporter.ImportResult result =
|
||||
@@ -98,7 +98,7 @@ public final class RlpBlockImporterTest {
|
||||
.privacyParameters(PrivacyParameters.DEFAULT)
|
||||
.dataDirectory(dataDir)
|
||||
.clock(TestClock.fixed())
|
||||
.transactionPoolConfiguration(TransactionPoolConfiguration.builder().build())
|
||||
.transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT)
|
||||
.gasLimitCalculator(GasLimitCalculator.constant())
|
||||
.build();
|
||||
|
||||
@@ -126,7 +126,7 @@ public final class RlpBlockImporterTest {
|
||||
.privacyParameters(PrivacyParameters.DEFAULT)
|
||||
.dataDirectory(dataDir)
|
||||
.clock(TestClock.fixed())
|
||||
.transactionPoolConfiguration(TransactionPoolConfiguration.builder().build())
|
||||
.transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT)
|
||||
.gasLimitCalculator(GasLimitCalculator.constant())
|
||||
.build();
|
||||
|
||||
@@ -166,7 +166,7 @@ public final class RlpBlockImporterTest {
|
||||
.privacyParameters(PrivacyParameters.DEFAULT)
|
||||
.dataDirectory(dataDir)
|
||||
.clock(TestClock.fixed())
|
||||
.transactionPoolConfiguration(TransactionPoolConfiguration.builder().build())
|
||||
.transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT)
|
||||
.gasLimitCalculator(GasLimitCalculator.constant())
|
||||
.build();
|
||||
final RlpBlockImporter.ImportResult result =
|
||||
|
||||
@@ -3411,6 +3411,18 @@ public class BesuCommandTest extends CommandTestAbstract {
|
||||
"Invalid value for option '--Xincoming-tx-messages-keep-alive-seconds': 'acbd' is not an int");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void eth65TrxAnnouncedBufferingPeriodWithInvalidInputShouldFail() {
|
||||
parseCommand("--Xeth65-tx-announced-buffering-period-milliseconds", "acbd");
|
||||
|
||||
Mockito.verifyZeroInteractions(mockRunnerBuilder);
|
||||
|
||||
assertThat(commandOutput.toString()).isEmpty();
|
||||
assertThat(commandErrorOutput.toString())
|
||||
.contains(
|
||||
"Invalid value for option '--Xeth65-tx-announced-buffering-period-milliseconds': 'acbd' is not a long");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tomlThatHasInvalidOptions() throws IOException {
|
||||
final URL configFile = this.getClass().getResource("/complete_config.toml");
|
||||
|
||||
@@ -17,12 +17,16 @@ package org.hyperledger.besu.cli.options;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.hyperledger.besu.cli.options.unstable.TransactionPoolOptions;
|
||||
import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration;
|
||||
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class TransactionPoolOptionsTest
|
||||
extends AbstractCLIOptionsTest<TransactionPoolConfiguration.Builder, TransactionPoolOptions> {
|
||||
extends AbstractCLIOptionsTest<
|
||||
ImmutableTransactionPoolConfiguration.Builder, TransactionPoolOptions> {
|
||||
|
||||
@Test
|
||||
public void txMessageKeepAliveSeconds() {
|
||||
@@ -40,20 +44,44 @@ public class TransactionPoolOptionsTest
|
||||
assertThat(commandErrorOutput.toString()).isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
TransactionPoolConfiguration.Builder createDefaultDomainObject() {
|
||||
return TransactionPoolConfiguration.builder();
|
||||
@Test
|
||||
public void eth65TrxAnnouncedBufferingPeriod() {
|
||||
final long eth65TrxAnnouncedBufferingPeriod = 999;
|
||||
final TestBesuCommand cmd =
|
||||
parseCommand(
|
||||
"--Xeth65-tx-announced-buffering-period-milliseconds",
|
||||
String.valueOf(eth65TrxAnnouncedBufferingPeriod));
|
||||
|
||||
final TransactionPoolOptions options = getOptionsFromBesuCommand(cmd);
|
||||
final TransactionPoolConfiguration config = options.toDomainObject().build();
|
||||
assertThat(config.getEth65TrxAnnouncedBufferingPeriod())
|
||||
.hasMillis(eth65TrxAnnouncedBufferingPeriod);
|
||||
|
||||
assertThat(commandOutput.toString()).isEmpty();
|
||||
assertThat(commandErrorOutput.toString()).isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
TransactionPoolConfiguration.Builder createCustomizedDomainObject() {
|
||||
return TransactionPoolConfiguration.builder()
|
||||
.txMessageKeepAliveSeconds(TransactionPoolConfiguration.DEFAULT_TX_MSG_KEEP_ALIVE + 1);
|
||||
ImmutableTransactionPoolConfiguration.Builder createDefaultDomainObject() {
|
||||
final ImmutableTransactionPoolConfiguration defaultValue =
|
||||
ImmutableTransactionPoolConfiguration.builder().build();
|
||||
return ImmutableTransactionPoolConfiguration.builder()
|
||||
.txMessageKeepAliveSeconds(defaultValue.getTxMessageKeepAliveSeconds())
|
||||
.eth65TrxAnnouncedBufferingPeriod(defaultValue.getEth65TrxAnnouncedBufferingPeriod());
|
||||
}
|
||||
|
||||
@Override
|
||||
ImmutableTransactionPoolConfiguration.Builder createCustomizedDomainObject() {
|
||||
return ImmutableTransactionPoolConfiguration.builder()
|
||||
.txMessageKeepAliveSeconds(TransactionPoolConfiguration.DEFAULT_TX_MSG_KEEP_ALIVE + 1)
|
||||
.eth65TrxAnnouncedBufferingPeriod(
|
||||
TransactionPoolConfiguration.ETH65_TRX_ANNOUNCED_BUFFERING_PERIOD.plus(
|
||||
Duration.ofMillis(100)));
|
||||
}
|
||||
|
||||
@Override
|
||||
TransactionPoolOptions optionsFromDomainObject(
|
||||
final TransactionPoolConfiguration.Builder domainObject) {
|
||||
final ImmutableTransactionPoolConfiguration.Builder domainObject) {
|
||||
return TransactionPoolOptions.fromConfig(domainObject.build());
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.hyperledger.besu.ethereum.eth.manager.EthPeers;
|
||||
import org.hyperledger.besu.ethereum.eth.manager.EthScheduler;
|
||||
import org.hyperledger.besu.ethereum.eth.sync.BlockBroadcaster;
|
||||
import org.hyperledger.besu.ethereum.eth.sync.state.SyncState;
|
||||
import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration;
|
||||
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool;
|
||||
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration;
|
||||
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolFactory;
|
||||
@@ -129,7 +130,7 @@ public class BesuEventsImplTest {
|
||||
blockBroadcaster = new BlockBroadcaster(mockEthContext);
|
||||
syncState = new SyncState(blockchain, mockEthPeers);
|
||||
TransactionPoolConfiguration txPoolConfig =
|
||||
TransactionPoolConfiguration.builder().txPoolMaxSize(1).build();
|
||||
ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(1).build();
|
||||
|
||||
transactionPool =
|
||||
TransactionPoolFactory.createTransactionPool(
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright ConsenSys AG.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package org.hyperledger.besu.ethereum.eth.manager.task;
|
||||
|
||||
import static org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration.MAX_PENDING_TRANSACTIONS;
|
||||
|
||||
import org.hyperledger.besu.ethereum.core.Hash;
|
||||
import org.hyperledger.besu.ethereum.eth.manager.EthPeer;
|
||||
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactionsMessageProcessor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
|
||||
import com.google.common.collect.EvictingQueue;
|
||||
import com.google.common.collect.Queues;
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public class BufferedGetPooledTransactionsFromPeerFetcher {
|
||||
|
||||
private static final int MAX_HASHES = 256;
|
||||
|
||||
private final EthPeer peer;
|
||||
private final PendingTransactionsMessageProcessor processor;
|
||||
private final Queue<Hash> txAnnounces;
|
||||
|
||||
public BufferedGetPooledTransactionsFromPeerFetcher(
|
||||
final EthPeer peer, final PendingTransactionsMessageProcessor processor) {
|
||||
this.peer = peer;
|
||||
this.processor = processor;
|
||||
this.txAnnounces = Queues.synchronizedQueue(EvictingQueue.create(MAX_PENDING_TRANSACTIONS));
|
||||
}
|
||||
|
||||
public void requestTransactions() {
|
||||
for (List<Hash> txAnnounces = getTxAnnounces();
|
||||
!txAnnounces.isEmpty();
|
||||
txAnnounces = getTxAnnounces()) {
|
||||
final GetPooledTransactionsFromPeerTask task =
|
||||
GetPooledTransactionsFromPeerTask.forHashes(
|
||||
processor.getEthContext(), txAnnounces, processor.getMetricsSystem());
|
||||
task.assignPeer(peer);
|
||||
processor
|
||||
.getEthContext()
|
||||
.getScheduler()
|
||||
.scheduleSyncWorkerTask(task)
|
||||
.thenAccept(
|
||||
result -> processor.getTransactionPool().addRemoteTransactions(result.getResult()));
|
||||
}
|
||||
}
|
||||
|
||||
public void addHash(final Hash hash) {
|
||||
txAnnounces.add(hash);
|
||||
}
|
||||
|
||||
private List<Hash> getTxAnnounces() {
|
||||
List<Hash> retrieved = new ArrayList<>();
|
||||
while (retrieved.size() < MAX_HASHES && !txAnnounces.isEmpty()) {
|
||||
final Hash txAnnounce = txAnnounces.poll();
|
||||
if (processor.getTransactionPool().getTransactionByHash(txAnnounce).isEmpty()) {
|
||||
retrieved.add(txAnnounce);
|
||||
}
|
||||
}
|
||||
return retrieved;
|
||||
}
|
||||
}
|
||||
@@ -18,10 +18,9 @@ import static java.time.Instant.now;
|
||||
import static org.apache.logging.log4j.LogManager.getLogger;
|
||||
|
||||
import org.hyperledger.besu.ethereum.core.Hash;
|
||||
import org.hyperledger.besu.ethereum.core.Transaction;
|
||||
import org.hyperledger.besu.ethereum.eth.manager.EthContext;
|
||||
import org.hyperledger.besu.ethereum.eth.manager.EthPeer;
|
||||
import org.hyperledger.besu.ethereum.eth.manager.task.GetPooledTransactionsFromPeerTask;
|
||||
import org.hyperledger.besu.ethereum.eth.manager.task.BufferedGetPooledTransactionsFromPeerFetcher;
|
||||
import org.hyperledger.besu.ethereum.eth.messages.NewPooledTransactionHashesMessage;
|
||||
import org.hyperledger.besu.ethereum.eth.sync.state.SyncState;
|
||||
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason;
|
||||
@@ -32,34 +31,39 @@ import org.hyperledger.besu.plugin.services.metrics.Counter;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
class PendingTransactionsMessageProcessor {
|
||||
public class PendingTransactionsMessageProcessor {
|
||||
|
||||
private static final int MAX_HASHES = 256;
|
||||
private static final int SKIPPED_MESSAGES_LOGGING_THRESHOLD = 1000;
|
||||
private static final long SYNC_TOLERANCE = 100L;
|
||||
|
||||
private static final Logger LOG = getLogger();
|
||||
|
||||
private final ConcurrentHashMap<EthPeer, BufferedGetPooledTransactionsFromPeerFetcher>
|
||||
scheduledTasks;
|
||||
|
||||
private final PeerPendingTransactionTracker transactionTracker;
|
||||
private final Counter totalSkippedTransactionsMessageCounter;
|
||||
private final TransactionPool transactionPool;
|
||||
private final TransactionPoolConfiguration transactionPoolConfiguration;
|
||||
private final EthContext ethContext;
|
||||
private final MetricsSystem metricsSystem;
|
||||
private final SyncState syncState;
|
||||
|
||||
PendingTransactionsMessageProcessor(
|
||||
public PendingTransactionsMessageProcessor(
|
||||
final PeerPendingTransactionTracker transactionTracker,
|
||||
final TransactionPool transactionPool,
|
||||
final TransactionPoolConfiguration transactionPoolConfiguration,
|
||||
final Counter metricsCounter,
|
||||
final EthContext ethContext,
|
||||
final MetricsSystem metricsSystem,
|
||||
final SyncState syncState) {
|
||||
this.transactionTracker = transactionTracker;
|
||||
this.transactionPool = transactionPool;
|
||||
this.transactionPoolConfiguration = transactionPoolConfiguration;
|
||||
this.ethContext = ethContext;
|
||||
this.metricsSystem = metricsSystem;
|
||||
this.syncState = syncState;
|
||||
@@ -71,6 +75,7 @@ class PendingTransactionsMessageProcessor {
|
||||
"{} expired transaction messages have been skipped.",
|
||||
SKIPPED_MESSAGES_LOGGING_THRESHOLD),
|
||||
SKIPPED_MESSAGES_LOGGING_THRESHOLD);
|
||||
this.scheduledTasks = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
void processNewPooledTransactionHashesMessage(
|
||||
@@ -86,36 +91,32 @@ class PendingTransactionsMessageProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
private void processNewPooledTransactionHashesMessage(
|
||||
final EthPeer peer, final NewPooledTransactionHashesMessage transactionsMessage) {
|
||||
try {
|
||||
LOG.trace("Received pooled transaction hashes message from {}", peer);
|
||||
|
||||
final List<Hash> pendingHashes = transactionsMessage.pendingTransactions();
|
||||
transactionTracker.markTransactionsHashesAsSeen(peer, pendingHashes);
|
||||
transactionTracker.markTransactionsHashesAsSeen(
|
||||
peer, transactionsMessage.pendingTransactions());
|
||||
if (syncState.isInSync(SYNC_TOLERANCE)) {
|
||||
final List<Hash> toRequest = new ArrayList<>();
|
||||
for (final Hash hash : pendingHashes) {
|
||||
if (transactionPool.addTransactionHash(hash)) {
|
||||
toRequest.add(hash);
|
||||
}
|
||||
}
|
||||
while (!toRequest.isEmpty()) {
|
||||
final List<Hash> messageHashes =
|
||||
toRequest.subList(0, Math.min(toRequest.size(), MAX_HASHES));
|
||||
final GetPooledTransactionsFromPeerTask task =
|
||||
GetPooledTransactionsFromPeerTask.forHashes(ethContext, messageHashes, metricsSystem);
|
||||
task.assignPeer(peer);
|
||||
ethContext
|
||||
.getScheduler()
|
||||
.scheduleSyncWorkerTask(task)
|
||||
.thenAccept(
|
||||
result -> {
|
||||
final List<Transaction> txs = result.getResult();
|
||||
transactionPool.addRemoteTransactions(txs);
|
||||
});
|
||||
final BufferedGetPooledTransactionsFromPeerFetcher bufferedTask =
|
||||
scheduledTasks.computeIfAbsent(
|
||||
peer,
|
||||
ethPeer -> {
|
||||
ethContext
|
||||
.getScheduler()
|
||||
.scheduleFutureTask(
|
||||
new FetcherCreatorTask(peer),
|
||||
transactionPoolConfiguration.getEth65TrxAnnouncedBufferingPeriod());
|
||||
return new BufferedGetPooledTransactionsFromPeerFetcher(peer, this);
|
||||
});
|
||||
|
||||
toRequest.removeAll(messageHashes);
|
||||
for (final Hash hash : transactionsMessage.pendingTransactions()) {
|
||||
if (transactionPool.getTransactionByHash(hash).isEmpty()
|
||||
&& transactionPool.addTransactionHash(hash)) {
|
||||
bufferedTask.addHash(hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (final RLPException ex) {
|
||||
@@ -126,4 +127,34 @@ class PendingTransactionsMessageProcessor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TransactionPool getTransactionPool() {
|
||||
return transactionPool;
|
||||
}
|
||||
|
||||
public EthContext getEthContext() {
|
||||
return ethContext;
|
||||
}
|
||||
|
||||
public MetricsSystem getMetricsSystem() {
|
||||
return metricsSystem;
|
||||
}
|
||||
|
||||
public class FetcherCreatorTask implements Runnable {
|
||||
final EthPeer peer;
|
||||
|
||||
public FetcherCreatorTask(final EthPeer peer) {
|
||||
this.peer = peer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (peer != null) {
|
||||
final BufferedGetPooledTransactionsFromPeerFetcher fetcher = scheduledTasks.remove(peer);
|
||||
if (!peer.isDisconnected()) {
|
||||
fetcher.requestTransactions();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,157 +17,55 @@ package org.hyperledger.besu.ethereum.eth.transactions;
|
||||
import org.hyperledger.besu.ethereum.core.Wei;
|
||||
import org.hyperledger.besu.util.number.Percentage;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.time.Duration;
|
||||
|
||||
public class TransactionPoolConfiguration {
|
||||
public static final int DEFAULT_TX_MSG_KEEP_ALIVE = 60;
|
||||
public static final int MAX_PENDING_TRANSACTIONS = 4096;
|
||||
public static final int MAX_PENDING_TRANSACTIONS_HASHES = 4096;
|
||||
public static final int DEFAULT_TX_RETENTION_HOURS = 13;
|
||||
public static final Percentage DEFAULT_PRICE_BUMP = Percentage.fromInt(10);
|
||||
public static final Wei DEFAULT_RPC_TX_FEE_CAP = Wei.fromEth(1);
|
||||
public static final TransactionPoolConfiguration DEFAULT =
|
||||
TransactionPoolConfiguration.builder().build();
|
||||
import org.immutables.value.Value;
|
||||
|
||||
private final int txPoolMaxSize;
|
||||
private final int pooledTransactionHashesSize;
|
||||
private final int pendingTxRetentionPeriod;
|
||||
private final int txMessageKeepAliveSeconds;
|
||||
private final Percentage priceBump;
|
||||
@Value.Immutable
|
||||
@Value.Style(allParameters = true)
|
||||
public interface TransactionPoolConfiguration {
|
||||
int DEFAULT_TX_MSG_KEEP_ALIVE = 60;
|
||||
int MAX_PENDING_TRANSACTIONS = 4096;
|
||||
int MAX_PENDING_TRANSACTIONS_HASHES = 4096;
|
||||
int DEFAULT_TX_RETENTION_HOURS = 13;
|
||||
Percentage DEFAULT_PRICE_BUMP = Percentage.fromInt(10);
|
||||
Wei DEFAULT_RPC_TX_FEE_CAP = Wei.fromEth(1);
|
||||
Duration ETH65_TRX_ANNOUNCED_BUFFERING_PERIOD = Duration.ofMillis(500);
|
||||
|
||||
private final Wei txFeeCap;
|
||||
TransactionPoolConfiguration DEFAULT = ImmutableTransactionPoolConfiguration.builder().build();
|
||||
|
||||
public TransactionPoolConfiguration(
|
||||
final int txPoolMaxSize,
|
||||
final int pooledTransactionHashesSize,
|
||||
final int pendingTxRetentionPeriod,
|
||||
final int txMessageKeepAliveSeconds,
|
||||
final Percentage priceBump,
|
||||
final Wei txFeeCap) {
|
||||
this.txPoolMaxSize = txPoolMaxSize;
|
||||
this.pooledTransactionHashesSize = pooledTransactionHashesSize;
|
||||
this.pendingTxRetentionPeriod = pendingTxRetentionPeriod;
|
||||
this.txMessageKeepAliveSeconds = txMessageKeepAliveSeconds;
|
||||
this.priceBump = priceBump;
|
||||
this.txFeeCap = txFeeCap;
|
||||
@Value.Default
|
||||
default int getTxPoolMaxSize() {
|
||||
return MAX_PENDING_TRANSACTIONS;
|
||||
}
|
||||
|
||||
public int getTxPoolMaxSize() {
|
||||
return txPoolMaxSize;
|
||||
@Value.Default
|
||||
default int getPooledTransactionHashesSize() {
|
||||
return MAX_PENDING_TRANSACTIONS_HASHES;
|
||||
}
|
||||
|
||||
public int getPooledTransactionHashesSize() {
|
||||
return pooledTransactionHashesSize;
|
||||
@Value.Default
|
||||
default int getPendingTxRetentionPeriod() {
|
||||
return DEFAULT_TX_RETENTION_HOURS;
|
||||
}
|
||||
|
||||
public int getPendingTxRetentionPeriod() {
|
||||
return pendingTxRetentionPeriod;
|
||||
@Value.Default
|
||||
default int getTxMessageKeepAliveSeconds() {
|
||||
return DEFAULT_TX_MSG_KEEP_ALIVE;
|
||||
}
|
||||
|
||||
public int getTxMessageKeepAliveSeconds() {
|
||||
return txMessageKeepAliveSeconds;
|
||||
@Value.Default
|
||||
default Percentage getPriceBump() {
|
||||
return DEFAULT_PRICE_BUMP;
|
||||
}
|
||||
|
||||
public Percentage getPriceBump() {
|
||||
return priceBump;
|
||||
@Value.Default
|
||||
default Duration getEth65TrxAnnouncedBufferingPeriod() {
|
||||
return ETH65_TRX_ANNOUNCED_BUFFERING_PERIOD;
|
||||
}
|
||||
|
||||
public Wei getTxFeeCap() {
|
||||
return txFeeCap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final TransactionPoolConfiguration that = (TransactionPoolConfiguration) o;
|
||||
return txPoolMaxSize == that.txPoolMaxSize
|
||||
&& Objects.equals(pendingTxRetentionPeriod, that.pendingTxRetentionPeriod)
|
||||
&& Objects.equals(txMessageKeepAliveSeconds, that.txMessageKeepAliveSeconds)
|
||||
&& Objects.equals(priceBump, that.priceBump)
|
||||
&& Objects.equals(txFeeCap, that.txFeeCap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(
|
||||
txPoolMaxSize, pendingTxRetentionPeriod, txMessageKeepAliveSeconds, priceBump, txFeeCap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TransactionPoolConfiguration{"
|
||||
+ "txPoolMaxSize="
|
||||
+ txPoolMaxSize
|
||||
+ ", pendingTxRetentionPeriod="
|
||||
+ pendingTxRetentionPeriod
|
||||
+ ", txMessageKeepAliveSeconds="
|
||||
+ txMessageKeepAliveSeconds
|
||||
+ ", priceBump="
|
||||
+ priceBump
|
||||
+ ", txFeeCap="
|
||||
+ txFeeCap
|
||||
+ '}';
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private int txPoolMaxSize = MAX_PENDING_TRANSACTIONS;
|
||||
private int pendingTxRetentionPeriod = DEFAULT_TX_RETENTION_HOURS;
|
||||
private Integer txMessageKeepAliveSeconds = DEFAULT_TX_MSG_KEEP_ALIVE;
|
||||
private int pooledTransactionHashesSize = MAX_PENDING_TRANSACTIONS_HASHES;
|
||||
private Percentage priceBump = DEFAULT_PRICE_BUMP;
|
||||
private Wei txFeeCap = DEFAULT_RPC_TX_FEE_CAP;
|
||||
|
||||
public Builder txPoolMaxSize(final int txPoolMaxSize) {
|
||||
this.txPoolMaxSize = txPoolMaxSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder pooledTransactionHashesSize(final int pooledTransactionHashesSize) {
|
||||
this.pooledTransactionHashesSize = pooledTransactionHashesSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder pendingTxRetentionPeriod(final int pendingTxRetentionPeriod) {
|
||||
this.pendingTxRetentionPeriod = pendingTxRetentionPeriod;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder txMessageKeepAliveSeconds(final int txMessageKeepAliveSeconds) {
|
||||
this.txMessageKeepAliveSeconds = txMessageKeepAliveSeconds;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder priceBump(final Percentage priceBump) {
|
||||
this.priceBump = priceBump;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder priceBump(final int priceBump) {
|
||||
return priceBump(Percentage.fromInt(priceBump));
|
||||
}
|
||||
|
||||
public Builder txFeeCap(final Wei txFeeCap) {
|
||||
this.txFeeCap = txFeeCap;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TransactionPoolConfiguration build() {
|
||||
return new TransactionPoolConfiguration(
|
||||
txPoolMaxSize,
|
||||
pooledTransactionHashesSize,
|
||||
pendingTxRetentionPeriod,
|
||||
txMessageKeepAliveSeconds,
|
||||
priceBump,
|
||||
txFeeCap);
|
||||
}
|
||||
@Value.Default
|
||||
default Wei getTxFeeCap() {
|
||||
return DEFAULT_RPC_TX_FEE_CAP;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,6 +146,7 @@ public class TransactionPoolFactory {
|
||||
new PendingTransactionsMessageProcessor(
|
||||
pendingTransactionTracker.get(),
|
||||
transactionPool,
|
||||
transactionPoolConfiguration,
|
||||
metricsSystem.createCounter(
|
||||
BesuMetricCategory.TRANSACTION_POOL,
|
||||
"pending_transactions_messages_skipped_total",
|
||||
|
||||
@@ -987,7 +987,7 @@ public final class EthProtocolManagerTest {
|
||||
metricsSystem,
|
||||
mock(SyncState.class),
|
||||
Wei.ZERO,
|
||||
TransactionPoolConfiguration.builder().build(),
|
||||
TransactionPoolConfiguration.DEFAULT,
|
||||
true,
|
||||
Optional.empty());
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ public abstract class AbstractMessageTaskTest<T, R> {
|
||||
metricsSystem,
|
||||
syncState,
|
||||
Wei.of(1),
|
||||
TransactionPoolConfiguration.builder().build(),
|
||||
TransactionPoolConfiguration.DEFAULT,
|
||||
true,
|
||||
Optional.empty());
|
||||
ethProtocolManager =
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright ConsenSys AG.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package org.hyperledger.besu.ethereum.eth.manager.task;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.hyperledger.besu.ethereum.core.BlockDataGenerator;
|
||||
import org.hyperledger.besu.ethereum.core.Hash;
|
||||
import org.hyperledger.besu.ethereum.core.Transaction;
|
||||
import org.hyperledger.besu.ethereum.eth.manager.EthContext;
|
||||
import org.hyperledger.besu.ethereum.eth.manager.EthPeer;
|
||||
import org.hyperledger.besu.ethereum.eth.manager.EthScheduler;
|
||||
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactionsMessageProcessor;
|
||||
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool;
|
||||
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
|
||||
import org.hyperledger.besu.plugin.services.MetricsSystem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class BufferedGetPooledTransactionsFromPeerFetcherTest {
|
||||
|
||||
@Mock EthPeer ethPeer;
|
||||
@Mock PendingTransactionsMessageProcessor processor;
|
||||
@Mock TransactionPool transactionPool;
|
||||
@Mock EthContext ethContext;
|
||||
@Mock EthScheduler ethScheduler;
|
||||
|
||||
@InjectMocks BufferedGetPooledTransactionsFromPeerFetcher fetcher;
|
||||
|
||||
private final MetricsSystem metricsSystem = new NoOpMetricsSystem();
|
||||
|
||||
private final BlockDataGenerator generator = new BlockDataGenerator();
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
when(processor.getTransactionPool()).thenReturn(transactionPool);
|
||||
when(processor.getMetricsSystem()).thenReturn(metricsSystem);
|
||||
when(processor.getEthContext()).thenReturn(ethContext);
|
||||
when(ethContext.getScheduler()).thenReturn(ethScheduler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestTransactionShouldStartTaskWhenUnknownTransaction() {
|
||||
|
||||
final Hash hash = generator.transaction().getHash();
|
||||
final List<Transaction> taskResult = Collections.singletonList(Transaction.builder().build());
|
||||
final AbstractPeerTask.PeerTaskResult<List<Transaction>> peerTaskResult =
|
||||
new AbstractPeerTask.PeerTaskResult<>(ethPeer, taskResult);
|
||||
when(ethScheduler.scheduleSyncWorkerTask(any(GetPooledTransactionsFromPeerTask.class)))
|
||||
.thenReturn(CompletableFuture.completedFuture(peerTaskResult));
|
||||
|
||||
fetcher.addHash(hash);
|
||||
fetcher.requestTransactions();
|
||||
|
||||
verify(ethScheduler).scheduleSyncWorkerTask(any(GetPooledTransactionsFromPeerTask.class));
|
||||
verifyNoMoreInteractions(ethScheduler);
|
||||
|
||||
verify(transactionPool, times(1)).addRemoteTransactions(taskResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestTransactionShouldSplitRequestIntoSeveralTasks() {
|
||||
for (int i = 0; i < 257; i++) {
|
||||
fetcher.addHash(generator.transaction().getHash());
|
||||
}
|
||||
final AbstractPeerTask.PeerTaskResult<List<Transaction>> peerTaskResult =
|
||||
new AbstractPeerTask.PeerTaskResult<>(ethPeer, new ArrayList<>());
|
||||
when(ethScheduler.scheduleSyncWorkerTask(any(GetPooledTransactionsFromPeerTask.class)))
|
||||
.thenReturn(CompletableFuture.completedFuture(peerTaskResult));
|
||||
|
||||
fetcher.requestTransactions();
|
||||
|
||||
verify(ethScheduler, times(2))
|
||||
.scheduleSyncWorkerTask(any(GetPooledTransactionsFromPeerTask.class));
|
||||
verifyNoMoreInteractions(ethScheduler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestTransactionShouldNotStartTaskWhenTransactionAlreadyInPool() {
|
||||
|
||||
final Hash hash = generator.transaction().getHash();
|
||||
when(transactionPool.getTransactionByHash(hash))
|
||||
.thenReturn(Optional.of(Transaction.builder().build()));
|
||||
|
||||
fetcher.addHash(hash);
|
||||
fetcher.requestTransactions();
|
||||
|
||||
verifyNoInteractions(ethScheduler);
|
||||
verify(transactionPool, never()).addRemoteTransactions(anyList());
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,10 @@ import static java.time.Duration.ofMillis;
|
||||
import static java.time.Duration.ofMinutes;
|
||||
import static java.time.Instant.now;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
@@ -26,16 +29,24 @@ import static org.mockito.Mockito.when;
|
||||
|
||||
import org.hyperledger.besu.ethereum.core.BlockDataGenerator;
|
||||
import org.hyperledger.besu.ethereum.core.Hash;
|
||||
import org.hyperledger.besu.ethereum.core.Transaction;
|
||||
import org.hyperledger.besu.ethereum.eth.manager.EthContext;
|
||||
import org.hyperledger.besu.ethereum.eth.manager.EthPeer;
|
||||
import org.hyperledger.besu.ethereum.eth.manager.EthScheduler;
|
||||
import org.hyperledger.besu.ethereum.eth.messages.NewPooledTransactionHashesMessage;
|
||||
import org.hyperledger.besu.ethereum.eth.sync.state.SyncState;
|
||||
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactionsMessageProcessor.FetcherCreatorTask;
|
||||
import org.hyperledger.besu.plugin.services.MetricsSystem;
|
||||
import org.hyperledger.besu.plugin.services.metrics.Counter;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
@@ -43,17 +54,38 @@ import org.mockito.junit.MockitoJUnitRunner;
|
||||
public class PendingTransactionsMessageProcessorTest {
|
||||
|
||||
@Mock private TransactionPool transactionPool;
|
||||
@Mock private TransactionPoolConfiguration transactionPoolConfiguration;
|
||||
@Mock private PeerPendingTransactionTracker transactionTracker;
|
||||
@Mock private Counter totalSkippedTransactionsMessageCounter;
|
||||
@Mock private EthPeer peer1;
|
||||
@Mock private MetricsSystem metricsSystem;
|
||||
@Mock private SyncState syncState;
|
||||
@InjectMocks private PendingTransactionsMessageProcessor messageHandler;
|
||||
@Mock private EthContext ethContext;
|
||||
@Mock private EthScheduler ethScheduler;
|
||||
|
||||
private PendingTransactionsMessageProcessor messageHandler;
|
||||
|
||||
private final BlockDataGenerator generator = new BlockDataGenerator();
|
||||
private final Hash hash1 = generator.transaction().getHash();
|
||||
private final Hash hash2 = generator.transaction().getHash();
|
||||
private final Hash hash3 = generator.transaction().getHash();
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
when(transactionPoolConfiguration.getEth65TrxAnnouncedBufferingPeriod())
|
||||
.thenReturn(Duration.ofMillis(500));
|
||||
messageHandler =
|
||||
new PendingTransactionsMessageProcessor(
|
||||
transactionTracker,
|
||||
transactionPool,
|
||||
transactionPoolConfiguration,
|
||||
totalSkippedTransactionsMessageCounter,
|
||||
ethContext,
|
||||
metricsSystem,
|
||||
syncState);
|
||||
when(ethContext.getScheduler()).thenReturn(ethScheduler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMarkAllReceivedTransactionsAsSeen() {
|
||||
messageHandler.processNewPooledTransactionHashesMessage(
|
||||
@@ -76,9 +108,35 @@ public class PendingTransactionsMessageProcessorTest {
|
||||
NewPooledTransactionHashesMessage.create(asList(hash1, hash2, hash3)),
|
||||
now(),
|
||||
ofMinutes(1));
|
||||
|
||||
verify(transactionPool).addTransactionHash(hash1);
|
||||
verify(transactionPool).addTransactionHash(hash2);
|
||||
verify(transactionPool).addTransactionHash(hash3);
|
||||
verify(transactionPool).getTransactionByHash(hash1);
|
||||
verify(transactionPool).getTransactionByHash(hash2);
|
||||
verify(transactionPool).getTransactionByHash(hash3);
|
||||
verifyNoMoreInteractions(transactionPool);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotAddAlreadyPresentTransactions() {
|
||||
when(syncState.isInSync(anyLong())).thenReturn(true);
|
||||
|
||||
when(transactionPool.getTransactionByHash(hash1))
|
||||
.thenReturn(Optional.of(Transaction.builder().build()));
|
||||
when(transactionPool.getTransactionByHash(hash2))
|
||||
.thenReturn(Optional.of(Transaction.builder().build()));
|
||||
|
||||
messageHandler.processNewPooledTransactionHashesMessage(
|
||||
peer1,
|
||||
NewPooledTransactionHashesMessage.create(asList(hash1, hash2, hash3)),
|
||||
now(),
|
||||
ofMinutes(1));
|
||||
|
||||
verify(transactionPool).addTransactionHash(hash3);
|
||||
verify(transactionPool).getTransactionByHash(hash1);
|
||||
verify(transactionPool).getTransactionByHash(hash2);
|
||||
verify(transactionPool).getTransactionByHash(hash3);
|
||||
verifyNoMoreInteractions(transactionPool);
|
||||
}
|
||||
|
||||
@@ -117,4 +175,42 @@ public class PendingTransactionsMessageProcessorTest {
|
||||
verify(totalSkippedTransactionsMessageCounter).inc(1);
|
||||
verifyNoMoreInteractions(totalSkippedTransactionsMessageCounter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldScheduleGetPooledTransactionsTaskWhenNewTransactionAdded() {
|
||||
when(syncState.isInSync(anyLong())).thenReturn(true);
|
||||
|
||||
final EthScheduler ethScheduler = mock(EthScheduler.class);
|
||||
when(ethContext.getScheduler()).thenReturn(ethScheduler);
|
||||
when(transactionPool.addTransactionHash(hash1)).thenReturn(true);
|
||||
|
||||
messageHandler.processNewPooledTransactionHashesMessage(
|
||||
peer1, NewPooledTransactionHashesMessage.create(asList(hash1, hash2)), now(), ofMinutes(1));
|
||||
|
||||
verify(ethScheduler, times(1))
|
||||
.scheduleFutureTask(any(FetcherCreatorTask.class), any(Duration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotScheduleGetPooledTransactionsTaskTwice() {
|
||||
when(syncState.isInSync(anyLong())).thenReturn(true);
|
||||
|
||||
when(transactionPool.addTransactionHash(hash1)).thenReturn(true);
|
||||
when(transactionPool.addTransactionHash(hash2)).thenReturn(true);
|
||||
|
||||
messageHandler.processNewPooledTransactionHashesMessage(
|
||||
peer1,
|
||||
NewPooledTransactionHashesMessage.create(Collections.singletonList(hash1)),
|
||||
now(),
|
||||
ofMinutes(1));
|
||||
|
||||
messageHandler.processNewPooledTransactionHashesMessage(
|
||||
peer1,
|
||||
NewPooledTransactionHashesMessage.create(Collections.singletonList(hash2)),
|
||||
now(),
|
||||
ofMinutes(1));
|
||||
|
||||
verify(ethScheduler, times(1))
|
||||
.scheduleFutureTask(any(FetcherCreatorTask.class), any(Duration.class));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ public class TestNode implements Closeable {
|
||||
metricsSystem,
|
||||
syncState,
|
||||
Wei.ZERO,
|
||||
TransactionPoolConfiguration.builder().build(),
|
||||
TransactionPoolConfiguration.DEFAULT,
|
||||
true,
|
||||
Optional.empty());
|
||||
|
||||
|
||||
@@ -90,12 +90,13 @@ public class TransactionPoolFactoryTest {
|
||||
new NoOpMetricsSystem(),
|
||||
state,
|
||||
Wei.of(1),
|
||||
new TransactionPoolConfiguration(
|
||||
ImmutableTransactionPoolConfiguration.of(
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
TransactionPoolConfiguration.DEFAULT_PRICE_BUMP,
|
||||
TransactionPoolConfiguration.ETH65_TRX_ANNOUNCED_BUFFERING_PERIOD,
|
||||
TransactionPoolConfiguration.DEFAULT_RPC_TX_FEE_CAP),
|
||||
pendingTransactions,
|
||||
peerTransactionTracker,
|
||||
@@ -175,12 +176,13 @@ public class TransactionPoolFactoryTest {
|
||||
new NoOpMetricsSystem(),
|
||||
state,
|
||||
Wei.of(1),
|
||||
new TransactionPoolConfiguration(
|
||||
ImmutableTransactionPoolConfiguration.of(
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
TransactionPoolConfiguration.DEFAULT_PRICE_BUMP,
|
||||
TransactionPoolConfiguration.ETH65_TRX_ANNOUNCED_BUFFERING_PERIOD,
|
||||
TransactionPoolConfiguration.DEFAULT_RPC_TX_FEE_CAP),
|
||||
pendingTransactions,
|
||||
peerTransactionTracker,
|
||||
|
||||
@@ -683,7 +683,7 @@ public class TransactionPoolTest {
|
||||
Wei.ZERO,
|
||||
metricsSystem,
|
||||
Optional.empty(),
|
||||
TransactionPoolConfiguration.builder().txFeeCap(Wei.ZERO).build());
|
||||
ImmutableTransactionPoolConfiguration.builder().txFeeCap(Wei.ZERO).build());
|
||||
when(transactionValidator.validate(any(Transaction.class), any(Optional.class)))
|
||||
.thenReturn(valid());
|
||||
when(transactionValidator.validateForSender(
|
||||
@@ -722,7 +722,7 @@ public class TransactionPoolTest {
|
||||
Wei.ZERO,
|
||||
metricsSystem,
|
||||
Optional.empty(),
|
||||
TransactionPoolConfiguration.builder().txFeeCap(twoEthers).build());
|
||||
ImmutableTransactionPoolConfiguration.builder().txFeeCap(twoEthers).build());
|
||||
|
||||
final TransactionTestFixture builder = new TransactionTestFixture();
|
||||
final Transaction transactionLocal =
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.hyperledger.besu.ethereum.eth.manager.EthMessages;
|
||||
import org.hyperledger.besu.ethereum.eth.manager.EthPeers;
|
||||
import org.hyperledger.besu.ethereum.eth.manager.EthScheduler;
|
||||
import org.hyperledger.besu.ethereum.eth.sync.state.SyncState;
|
||||
import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration;
|
||||
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions;
|
||||
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool;
|
||||
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration;
|
||||
@@ -190,7 +191,7 @@ public class RetestethContext {
|
||||
final EthContext ethContext = new EthContext(ethPeers, new EthMessages(), ethScheduler);
|
||||
|
||||
final TransactionPoolConfiguration transactionPoolConfiguration =
|
||||
TransactionPoolConfiguration.builder().build();
|
||||
ImmutableTransactionPoolConfiguration.builder().build();
|
||||
|
||||
transactionPool =
|
||||
TransactionPoolFactory.createTransactionPool(
|
||||
|
||||
Reference in New Issue
Block a user