TxPool code refactor to improve readability (#4566)

Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>
pull/4595/head
Fabio Di Fabio 2 years ago committed by GitHub
parent 42260fd56b
commit 166aa69531
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/PendingStateAdapter.java
  2. 3
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TxPoolBesuPendingTransactions.java
  3. 8
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TxPoolBesuStatistics.java
  4. 2
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TxPoolBesuTransactions.java
  5. 12
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/PendingTransactionResult.java
  6. 16
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/PendingTransactionsResult.java
  7. 41
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/transaction/pool/PendingTransactionFilter.java
  8. 6
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/AbstractEthGraphQLHttpServiceTest.java
  9. 14
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionByHashTest.java
  10. 13
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TxPoolBesuPendingTransactionsTest.java
  11. 18
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TxPoolBesuStatisticsTest.java
  12. 24
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TxPoolBesuTransactionsTest.java
  13. 14
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/transaction/pool/PendingTransactionFilterTest.java
  14. 117
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java
  15. 42
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionAddedStatus.java
  16. 12
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionBroadcaster.java
  17. 9
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPool.java
  18. 13
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolReplacementHandler.java
  19. 7
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolReplacementRule.java
  20. 16
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementByFeeMarketRule.java
  21. 15
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementByGasPriceRule.java
  22. 445
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractPendingTransactionsSorter.java
  23. 197
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/BaseFeePendingTransactionsSorter.java
  24. 49
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/GasPricePendingTransactionsSorter.java
  25. 206
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/LowestInvalidNonceCache.java
  26. 62
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/PendingTransactionsForSender.java
  27. 2
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/GetPooledTransactionsFromPeerTaskTest.java
  28. 6
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/AbstractPendingTransactionsTestBase.java
  29. 2
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PendingMultiTypesTransactionsTest.java
  30. 17
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionBroadcasterTest.java
  31. 23
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolReplacementHandlerTest.java
  32. 27
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementRulesTest.java

@ -19,6 +19,7 @@ import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.graphql.GraphQLContextType;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.api.query.TransactionWithMetadata;
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.transaction.CallParameter;
@ -48,8 +49,8 @@ public class PendingStateAdapter extends AdapterBase {
}
public List<TransactionAdapter> getTransactions() {
return pendingTransactions.getTransactionInfo().stream()
.map(AbstractPendingTransactionsSorter.TransactionInfo::getTransaction)
return pendingTransactions.getPendingTransactions().stream()
.map(PendingTransaction::getTransaction)
.map(TransactionWithMetadata::new)
.map(TransactionAdapter::new)
.collect(Collectors.toList());

@ -58,7 +58,8 @@ public class TxPoolBesuPendingTransactions implements JsonRpcMethod {
.orElse(Collections.emptyList());
final Set<Transaction> pendingTransactionsFiltered =
pendingTransactionFilter.reduce(pendingTransactions.getTransactionInfo(), filters, limit);
pendingTransactionFilter.reduce(
pendingTransactions.getPendingTransactions(), filters, limit);
return new JsonRpcSuccessResponse(
requestContext.getRequest().getId(),

@ -19,8 +19,8 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.PendingTransactionsStatisticsResult;
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionInfo;
import java.util.Set;
@ -43,10 +43,10 @@ public class TxPoolBesuStatistics implements JsonRpcMethod {
}
private PendingTransactionsStatisticsResult statistics() {
final Set<TransactionInfo> transactionInfo = pendingTransactions.getTransactionInfo();
final Set<PendingTransaction> pendingTransaction = pendingTransactions.getPendingTransactions();
final long localCount =
transactionInfo.stream().filter(TransactionInfo::isReceivedFromLocalSource).count();
final long remoteCount = transactionInfo.size() - localCount;
pendingTransaction.stream().filter(PendingTransaction::isReceivedFromLocalSource).count();
final long remoteCount = pendingTransaction.size() - localCount;
return new PendingTransactionsStatisticsResult(
pendingTransactions.maxSize(), localCount, remoteCount);
}

@ -39,7 +39,7 @@ public class TxPoolBesuTransactions implements JsonRpcMethod {
final JsonRpcSuccessResponse jsonRpcSuccessResponse =
new JsonRpcSuccessResponse(
requestContext.getRequest().getId(),
new PendingTransactionsResult(pendingTransactions.getTransactionInfo()));
new PendingTransactionsResult(pendingTransactions.getPendingTransactions()));
return jsonRpcSuccessResponse;
}
}

@ -14,7 +14,7 @@
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionInfo;
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction;
import java.time.Instant;
@ -22,16 +22,16 @@ import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
@JsonPropertyOrder({"hash", "isReceivedFromLocalSource"})
public class TransactionInfoResult implements TransactionResult {
public class PendingTransactionResult implements TransactionResult {
private final String hash;
private final boolean isReceivedFromLocalSource;
private final Instant addedToPoolAt;
public TransactionInfoResult(final TransactionInfo transactionInfo) {
hash = transactionInfo.getHash().toString();
isReceivedFromLocalSource = transactionInfo.isReceivedFromLocalSource();
addedToPoolAt = transactionInfo.getAddedToPoolAt();
public PendingTransactionResult(final PendingTransaction pendingTransaction) {
hash = pendingTransaction.getHash().toString();
isReceivedFromLocalSource = pendingTransaction.isReceivedFromLocalSource();
addedToPoolAt = pendingTransaction.getAddedToPoolAt();
}
@JsonGetter(value = "hash")

@ -14,7 +14,7 @@
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionInfo;
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction;
import java.util.Set;
import java.util.stream.Collectors;
@ -23,15 +23,17 @@ import com.fasterxml.jackson.annotation.JsonValue;
public class PendingTransactionsResult implements TransactionResult {
private final Set<TransactionInfoResult> transactionInfoResults;
private final Set<PendingTransactionResult> pendingTransactionResults;
public PendingTransactionsResult(final Set<TransactionInfo> transactionInfoSet) {
transactionInfoResults =
transactionInfoSet.stream().map(TransactionInfoResult::new).collect(Collectors.toSet());
public PendingTransactionsResult(final Set<PendingTransaction> pendingTransactionSet) {
pendingTransactionResults =
pendingTransactionSet.stream()
.map(PendingTransactionResult::new)
.collect(Collectors.toSet());
}
@JsonValue
public Set<TransactionInfoResult> getResults() {
return transactionInfoResults;
public Set<PendingTransactionResult> getResults() {
return pendingTransactionResults;
}
}

@ -21,7 +21,7 @@ import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionInfo;
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction;
import java.util.List;
import java.util.Optional;
@ -44,16 +44,19 @@ public class PendingTransactionFilter {
public static final String NONCE_FIELD = "nonce";
public Set<Transaction> reduce(
final Set<TransactionInfo> pendingTransactions, final List<Filter> filters, final int limit)
final Set<PendingTransaction> pendingTransactions,
final List<Filter> filters,
final int limit)
throws InvalidJsonRpcParameters {
return pendingTransactions.stream()
.filter(transactionInfo -> applyFilters(transactionInfo, filters))
.filter(pendingTx -> applyFilters(pendingTx, filters))
.limit(limit)
.map(TransactionInfo::getTransaction)
.map(PendingTransaction::getTransaction)
.collect(Collectors.toSet());
}
private boolean applyFilters(final TransactionInfo transactionInfo, final List<Filter> filters)
private boolean applyFilters(
final PendingTransaction pendingTransaction, final List<Filter> filters)
throws InvalidJsonRpcParameters {
boolean isValid = true;
for (Filter filter : filters) {
@ -61,24 +64,26 @@ public class PendingTransactionFilter {
final String value = filter.getFieldValue();
switch (filter.getFieldName()) {
case FROM_FIELD:
isValid = validateFrom(transactionInfo, predicate, value);
isValid = validateFrom(pendingTransaction, predicate, value);
break;
case TO_FIELD:
isValid = validateTo(transactionInfo, predicate, value);
isValid = validateTo(pendingTransaction, predicate, value);
break;
case GAS_PRICE_FIELD:
isValid =
validateWei(transactionInfo.getTransaction().getGasPrice().get(), predicate, value);
validateWei(
pendingTransaction.getTransaction().getGasPrice().get(), predicate, value);
break;
case GAS_FIELD:
isValid =
validateWei(Wei.of(transactionInfo.getTransaction().getGasLimit()), predicate, value);
validateWei(
Wei.of(pendingTransaction.getTransaction().getGasLimit()), predicate, value);
break;
case VALUE_FIELD:
isValid = validateWei(transactionInfo.getTransaction().getValue(), predicate, value);
isValid = validateWei(pendingTransaction.getTransaction().getValue(), predicate, value);
break;
case NONCE_FIELD:
isValid = validateNonce(transactionInfo, predicate, value);
isValid = validateNonce(pendingTransaction, predicate, value);
break;
}
if (!isValid) {
@ -89,31 +94,31 @@ public class PendingTransactionFilter {
}
private boolean validateFrom(
final TransactionInfo transactionInfo, final Predicate predicate, final String value)
final PendingTransaction pendingTransaction, final Predicate predicate, final String value)
throws InvalidJsonRpcParameters {
return predicate
.getOperator()
.apply(transactionInfo.getTransaction().getSender(), Address.fromHexString(value));
.apply(pendingTransaction.getTransaction().getSender(), Address.fromHexString(value));
}
private boolean validateTo(
final TransactionInfo transactionInfo, final Predicate predicate, final String value)
final PendingTransaction pendingTransaction, final Predicate predicate, final String value)
throws InvalidJsonRpcParameters {
final Optional<Address> maybeTo = transactionInfo.getTransaction().getTo();
final Optional<Address> maybeTo = pendingTransaction.getTransaction().getTo();
if (maybeTo.isPresent() && predicate.equals(EQ)) {
return predicate.getOperator().apply(maybeTo.get(), Address.fromHexString(value));
} else if (predicate.equals(ACTION)) {
return transactionInfo.getTransaction().isContractCreation();
return pendingTransaction.getTransaction().isContractCreation();
}
return false;
}
private boolean validateNonce(
final TransactionInfo transactionInfo, final Predicate predicate, final String value)
final PendingTransaction pendingTransaction, final Predicate predicate, final String value)
throws InvalidJsonRpcParameters {
return predicate
.getOperator()
.apply(transactionInfo.getTransaction().getNonce(), Long.decode(value));
.apply(pendingTransaction.getTransaction().getNonce(), Long.decode(value));
}
private boolean validateWei(

@ -33,8 +33,8 @@ import org.hyperledger.besu.ethereum.core.Synchronizer;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.eth.EthProtocol;
import org.hyperledger.besu.ethereum.eth.manager.EthScheduler;
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.GasPricePendingTransactionsSorter;
import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode;
import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions;
@ -143,10 +143,10 @@ public abstract class AbstractEthGraphQLHttpServiceTest {
final GasPricePendingTransactionsSorter pendingTransactionsMock =
Mockito.mock(GasPricePendingTransactionsSorter.class);
Mockito.when(transactionPoolMock.getPendingTransactions()).thenReturn(pendingTransactionsMock);
Mockito.when(pendingTransactionsMock.getTransactionInfo())
Mockito.when(pendingTransactionsMock.getPendingTransactions())
.thenReturn(
Collections.singleton(
new AbstractPendingTransactionsSorter.TransactionInfo(
new PendingTransaction(
Transaction.builder()
.type(TransactionType.FRONTIER)
.nonce(42)

@ -32,7 +32,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.TransactionPen
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.api.query.TransactionWithMetadata;
import org.hyperledger.besu.ethereum.core.BlockDataGenerator;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter;
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.GasPricePendingTransactionsSorter;
import org.hyperledger.besu.plugin.data.Transaction;
@ -159,11 +159,10 @@ public class EthGetTransactionByHashTest {
@Test
public void validateResultSpec() {
AbstractPendingTransactionsSorter.TransactionInfo tInfo =
getPendingTransactions().stream().findFirst().get();
Hash hash = tInfo.getHash();
PendingTransaction pendingTx = getPendingTransactions().stream().findFirst().get();
Hash hash = pendingTx.getHash();
when(this.pendingTransactions.getTransactionByHash(hash))
.thenReturn(Optional.of(tInfo.getTransaction()));
.thenReturn(Optional.of(pendingTx.getTransaction()));
final JsonRpcRequestContext request =
new JsonRpcRequestContext(
new JsonRpcRequest(JSON_RPC_VERSION, ETH_METHOD, new Object[] {hash}));
@ -190,7 +189,7 @@ public class EthGetTransactionByHashTest {
assertThat(result.getS()).isNotNull();
}
private Set<AbstractPendingTransactionsSorter.TransactionInfo> getPendingTransactions() {
private Set<PendingTransaction> getPendingTransactions() {
final BlockDataGenerator gen = new BlockDataGenerator();
Transaction pendingTransaction = gen.transaction();
@ -198,8 +197,7 @@ public class EthGetTransactionByHashTest {
return gen.transactionsWithAllTypes(4).stream()
.map(
transaction ->
new AbstractPendingTransactionsSorter.TransactionInfo(
transaction, true, Instant.ofEpochSecond(Integer.MAX_VALUE)))
new PendingTransaction(transaction, true, Instant.ofEpochSecond(Integer.MAX_VALUE)))
.collect(Collectors.toUnmodifiableSet());
}
}

@ -25,7 +25,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.PendingTran
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.TransactionPendingResult;
import org.hyperledger.besu.ethereum.core.BlockDataGenerator;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter;
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.GasPricePendingTransactionsSorter;
import java.time.Instant;
@ -52,9 +52,9 @@ public class TxPoolBesuPendingTransactionsTest {
@Before
public void setUp() {
final Set<AbstractPendingTransactionsSorter.TransactionInfo> listTrx = getPendingTransactions();
final Set<PendingTransaction> listTrx = getPendingTransactions();
method = new TxPoolBesuPendingTransactions(pendingTransactions);
when(this.pendingTransactions.getTransactionInfo()).thenReturn(listTrx);
when(this.pendingTransactions.getPendingTransactions()).thenReturn(listTrx);
}
@Test
@ -120,7 +120,7 @@ public class TxPoolBesuPendingTransactionsTest {
final Map<String, String> fromFilter = new HashMap<>();
fromFilter.put(
"eq",
pendingTransactions.getTransactionInfo().stream()
pendingTransactions.getPendingTransactions().stream()
.findAny()
.get()
.getTransaction()
@ -259,14 +259,13 @@ public class TxPoolBesuPendingTransactionsTest {
.hasMessageContaining("The `to` filter only supports the `eq` or `action` operator");
}
private Set<AbstractPendingTransactionsSorter.TransactionInfo> getPendingTransactions() {
private Set<PendingTransaction> getPendingTransactions() {
final BlockDataGenerator gen = new BlockDataGenerator();
return gen.transactionsWithAllTypes(4).stream()
.map(
transaction ->
new AbstractPendingTransactionsSorter.TransactionInfo(
transaction, true, Instant.ofEpochSecond(Integer.MAX_VALUE)))
new PendingTransaction(transaction, true, Instant.ofEpochSecond(Integer.MAX_VALUE)))
.collect(Collectors.toUnmodifiableSet());
}
}

@ -22,7 +22,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.PendingTransactionsStatisticsResult;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionInfo;
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.GasPricePendingTransactionsSorter;
import com.google.common.collect.Sets;
@ -57,11 +57,11 @@ public class TxPoolBesuStatisticsTest {
new JsonRpcRequest(
JSON_RPC_VERSION, TXPOOL_PENDING_TRANSACTIONS_METHOD, new Object[] {}));
final TransactionInfo local = createTransactionInfo(true);
final TransactionInfo secondLocal = createTransactionInfo(true);
final TransactionInfo remote = createTransactionInfo(false);
final PendingTransaction local = createTransactionInfo(true);
final PendingTransaction secondLocal = createTransactionInfo(true);
final PendingTransaction remote = createTransactionInfo(false);
when(pendingTransactions.maxSize()).thenReturn(123L);
when(pendingTransactions.getTransactionInfo())
when(pendingTransactions.getPendingTransactions())
.thenReturn(Sets.newHashSet(local, secondLocal, remote));
final JsonRpcSuccessResponse actualResponse = (JsonRpcSuccessResponse) method.response(request);
@ -72,9 +72,9 @@ public class TxPoolBesuStatisticsTest {
assertThat(result.getMaxSize()).isEqualTo(123);
}
private TransactionInfo createTransactionInfo(final boolean local) {
final TransactionInfo transactionInfo = mock(TransactionInfo.class);
when(transactionInfo.isReceivedFromLocalSource()).thenReturn(local);
return transactionInfo;
private PendingTransaction createTransactionInfo(final boolean local) {
final PendingTransaction pendingTransaction = mock(PendingTransaction.class);
when(pendingTransaction.isReceivedFromLocalSource()).thenReturn(local);
return pendingTransaction;
}
}

@ -22,9 +22,9 @@ import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.PendingTransactionResult;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.PendingTransactionsResult;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.TransactionInfoResult;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionInfo;
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.GasPricePendingTransactionsSorter;
import java.time.Instant;
@ -64,20 +64,20 @@ public class TxPoolBesuTransactionsTest {
new JsonRpcRequest(
JSON_RPC_VERSION, TXPOOL_PENDING_TRANSACTIONS_METHOD, new Object[] {}));
TransactionInfo transactionInfo = mock(TransactionInfo.class);
when(transactionInfo.getHash()).thenReturn(Hash.fromHexString(TRANSACTION_HASH));
when(transactionInfo.isReceivedFromLocalSource()).thenReturn(true);
when(transactionInfo.getAddedToPoolAt()).thenReturn(addedAt);
when(pendingTransactions.getTransactionInfo()).thenReturn(Sets.newHashSet(transactionInfo));
PendingTransaction pendingTransaction = mock(PendingTransaction.class);
when(pendingTransaction.getHash()).thenReturn(Hash.fromHexString(TRANSACTION_HASH));
when(pendingTransaction.isReceivedFromLocalSource()).thenReturn(true);
when(pendingTransaction.getAddedToPoolAt()).thenReturn(addedAt);
when(pendingTransactions.getPendingTransactions())
.thenReturn(Sets.newHashSet(pendingTransaction));
final JsonRpcSuccessResponse actualResponse = (JsonRpcSuccessResponse) method.response(request);
final PendingTransactionsResult result = (PendingTransactionsResult) actualResponse.getResult();
final TransactionInfoResult actualTransactionInfo =
result.getResults().stream().findFirst().get();
final PendingTransactionResult actualResult = result.getResults().stream().findFirst().get();
assertThat(actualTransactionInfo.getHash()).isEqualTo(TRANSACTION_HASH);
assertThat(actualTransactionInfo.isReceivedFromLocalSource()).isTrue();
assertThat(actualTransactionInfo.getAddedToPoolAt()).isEqualTo(addedAt.toString());
assertThat(actualResult.getHash()).isEqualTo(TRANSACTION_HASH);
assertThat(actualResult.isReceivedFromLocalSource()).isTrue();
assertThat(actualResult.getAddedToPoolAt()).isEqualTo(addedAt.toString());
}
}

@ -30,7 +30,7 @@ import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool.PendingTransactionFilter.Filter;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter;
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction;
import java.time.Instant;
import java.util.ArrayList;
@ -123,9 +123,8 @@ public class PendingTransactionFilterTest {
}
}
private Set<AbstractPendingTransactionsSorter.TransactionInfo> getPendingTransactions() {
final List<AbstractPendingTransactionsSorter.TransactionInfo> transactionInfoList =
new ArrayList<>();
private Set<PendingTransaction> getPendingTransactions() {
final List<PendingTransaction> pendingTransactionList = new ArrayList<>();
final int numberTrx = 5;
for (int i = 1; i < numberTrx; i++) {
Transaction transaction = mock(Transaction.class);
@ -140,10 +139,9 @@ public class PendingTransactionFilterTest {
if (i == numberTrx - 1) {
when(transaction.isContractCreation()).thenReturn(true);
}
transactionInfoList.add(
new AbstractPendingTransactionsSorter.TransactionInfo(
transaction, true, Instant.ofEpochSecond(Integer.MAX_VALUE)));
pendingTransactionList.add(
new PendingTransaction(transaction, true, Instant.ofEpochSecond(Integer.MAX_VALUE)));
}
return new LinkedHashSet<>(transactionInfoList);
return new LinkedHashSet<>(pendingTransactionList);
}
}

@ -0,0 +1,117 @@
/*
* Copyright Besu contributors.
*
* 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.transactions;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.Transaction;
import java.time.Instant;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
/**
* Tracks the additional metadata associated with transactions to enable prioritization for mining
* and deciding which transactions to drop when the transaction pool reaches its size limit.
*/
public class PendingTransaction {
private static final AtomicLong TRANSACTIONS_ADDED = new AtomicLong();
private final Transaction transaction;
private final boolean receivedFromLocalSource;
private final Instant addedToPoolAt;
private final long sequence; // Allows prioritization based on order transactions are added
public PendingTransaction(
final Transaction transaction,
final boolean receivedFromLocalSource,
final Instant addedToPoolAt) {
this.transaction = transaction;
this.receivedFromLocalSource = receivedFromLocalSource;
this.addedToPoolAt = addedToPoolAt;
this.sequence = TRANSACTIONS_ADDED.getAndIncrement();
}
public Transaction getTransaction() {
return transaction;
}
public Wei getGasPrice() {
return transaction.getGasPrice().orElse(Wei.ZERO);
}
public long getSequence() {
return sequence;
}
public long getNonce() {
return transaction.getNonce();
}
public Address getSender() {
return transaction.getSender();
}
public boolean isReceivedFromLocalSource() {
return receivedFromLocalSource;
}
public Hash getHash() {
return transaction.getHash();
}
public Instant getAddedToPoolAt() {
return addedToPoolAt;
}
public static List<Transaction> toTransactionList(
final Collection<PendingTransaction> transactionsInfo) {
return transactionsInfo.stream()
.map(PendingTransaction::getTransaction)
.collect(Collectors.toUnmodifiableList());
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PendingTransaction that = (PendingTransaction) o;
return sequence == that.sequence;
}
@Override
public int hashCode() {
return 31 * (int) (sequence ^ (sequence >>> 32));
}
public String toTraceLog() {
return "{sequence: "
+ sequence
+ ", addedAt: "
+ addedToPoolAt
+ ", "
+ transaction.toTraceLog()
+ "}";
}
}

@ -0,0 +1,42 @@
/*
* Copyright Besu contributors.
*
* 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.transactions;
import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason;
import java.util.Optional;
public enum TransactionAddedStatus {
ALREADY_KNOWN(TransactionInvalidReason.TRANSACTION_ALREADY_KNOWN),
REJECTED_UNDERPRICED_REPLACEMENT(TransactionInvalidReason.TRANSACTION_REPLACEMENT_UNDERPRICED),
NONCE_TOO_FAR_IN_FUTURE_FOR_SENDER(TransactionInvalidReason.NONCE_TOO_FAR_IN_FUTURE_FOR_SENDER),
LOWER_NONCE_INVALID_TRANSACTION_KNOWN(
TransactionInvalidReason.LOWER_NONCE_INVALID_TRANSACTION_EXISTS),
ADDED();
private final Optional<TransactionInvalidReason> invalidReason;
TransactionAddedStatus() {
this.invalidReason = Optional.empty();
}
TransactionAddedStatus(final TransactionInvalidReason invalidReason) {
this.invalidReason = Optional.of(invalidReason);
}
public Optional<TransactionInvalidReason> getInvalidReason() {
return invalidReason;
}
}

@ -14,7 +14,7 @@
*/
package org.hyperledger.besu.ethereum.eth.transactions;
import static org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionInfo.toTransactionList;
import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.toTransactionList;
import static org.hyperledger.besu.util.Slf4jLambdaHelper.traceLambda;
import org.hyperledger.besu.ethereum.core.Transaction;
@ -23,7 +23,6 @@ import org.hyperledger.besu.ethereum.eth.manager.EthPeer;
import org.hyperledger.besu.ethereum.eth.messages.EthPV65;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool.TransactionBatchAddedListener;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionInfo;
import java.util.ArrayList;
import java.util.Collections;
@ -59,12 +58,13 @@ public class TransactionBroadcaster implements TransactionBatchAddedListener {
}
public void relayTransactionPoolTo(final EthPeer peer) {
Set<TransactionInfo> pendingTransactionInfo = pendingTransactions.getTransactionInfo();
if (!pendingTransactionInfo.isEmpty()) {
Set<PendingTransaction> pendingPendingTransaction =
pendingTransactions.getPendingTransactions();
if (!pendingPendingTransaction.isEmpty()) {
if (peer.hasSupportForMessage(EthPV65.NEW_POOLED_TRANSACTION_HASHES)) {
sendTransactionHashes(toTransactionList(pendingTransactionInfo), List.of(peer));
sendTransactionHashes(toTransactionList(pendingPendingTransaction), List.of(peer));
} else {
sendFullTransactions(toTransactionList(pendingTransactionInfo), List.of(peer));
sendFullTransactions(toTransactionList(pendingPendingTransaction), List.of(peer));
}
}
}

@ -16,8 +16,8 @@ package org.hyperledger.besu.ethereum.eth.transactions;
import static java.util.Collections.singletonList;
import static java.util.Optional.ofNullable;
import static org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionAddedStatus.ADDED;
import static org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionAddedStatus.ALREADY_KNOWN;
import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedStatus.ADDED;
import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedStatus.ALREADY_KNOWN;
import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.CHAIN_HEAD_NOT_AVAILABLE;
import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.CHAIN_HEAD_WORLD_STATE_NOT_AVAILABLE;
import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.INTERNAL_ERROR;
@ -35,7 +35,6 @@ 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.transactions.sorter.AbstractPendingTransactionsSorter;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionAddedStatus;
import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionValidator;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams;
@ -166,7 +165,7 @@ public class TransactionPool implements BlockAddedObserver {
transaction::toTraceLog,
miningParameters::getMinTransactionGasPrice);
pendingTransactions
.signalInvalidTransaction(transaction)
.signalInvalidAndGetDependentTransactions(transaction)
.forEach(pendingTransactions::removeTransaction);
continue;
}
@ -193,7 +192,7 @@ public class TransactionPool implements BlockAddedObserver {
transaction::toTraceLog,
validationResult.result::getInvalidReason);
pendingTransactions
.signalInvalidTransaction(transaction)
.signalInvalidAndGetDependentTransactions(transaction)
.forEach(pendingTransactions::removeTransaction);
}
}

@ -17,7 +17,6 @@ package org.hyperledger.besu.ethereum.eth.transactions;
import static java.util.Arrays.asList;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionInfo;
import org.hyperledger.besu.util.number.Percentage;
import java.util.List;
@ -40,15 +39,17 @@ public class TransactionPoolReplacementHandler {
}
public boolean shouldReplace(
final TransactionInfo existingTransactionInfo,
final TransactionInfo newTransactionInfo,
final PendingTransaction existingPendingTransaction,
final PendingTransaction newPendingTransaction,
final BlockHeader chainHeadHeader) {
assert existingTransactionInfo != null;
return newTransactionInfo != null
assert existingPendingTransaction != null;
return newPendingTransaction != null
&& rules.stream()
.anyMatch(
rule ->
rule.shouldReplace(
existingTransactionInfo, newTransactionInfo, chainHeadHeader.getBaseFee()));
existingPendingTransaction,
newPendingTransaction,
chainHeadHeader.getBaseFee()));
}
}

@ -15,7 +15,6 @@
package org.hyperledger.besu.ethereum.eth.transactions;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionInfo;
import java.util.Optional;
@ -23,11 +22,11 @@ import java.util.Optional;
public interface TransactionPoolReplacementRule {
boolean shouldReplace(
TransactionInfo existingTransactionInfo,
TransactionInfo newTransactionInfo,
PendingTransaction existingPendingTransaction,
PendingTransaction newPendingTransaction,
Optional<Wei> baseFee);
default boolean isNotGasPriced(final TransactionInfo tInfo) {
default boolean isNotGasPriced(final PendingTransaction tInfo) {
return tInfo.getTransaction().getType().supports1559FeeMarket();
}
}

@ -17,7 +17,6 @@ package org.hyperledger.besu.ethereum.eth.transactions;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.feemarket.TransactionPriceCalculator;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionInfo;
import org.hyperledger.besu.plugin.data.TransactionType;
import org.hyperledger.besu.util.number.Percentage;
@ -37,27 +36,28 @@ public class TransactionReplacementByFeeMarketRule implements TransactionPoolRep
@Override
public boolean shouldReplace(
final TransactionInfo existingTransactionInfo,
final TransactionInfo newTransactionInfo,
final PendingTransaction existingPendingTransaction,
final PendingTransaction newPendingTransaction,
final Optional<Wei> baseFee) {
// bail early if basefee is absent or neither transaction supports 1559 fee market
if (baseFee.isEmpty()
|| !(isNotGasPriced(existingTransactionInfo) || isNotGasPriced(newTransactionInfo))) {
|| !(isNotGasPriced(existingPendingTransaction) || isNotGasPriced(newPendingTransaction))) {
return false;
}
Wei newEffPrice = priceOf(newTransactionInfo.getTransaction(), baseFee);
Wei newEffPriority = newTransactionInfo.getTransaction().getEffectivePriorityFeePerGas(baseFee);
Wei newEffPrice = priceOf(newPendingTransaction.getTransaction(), baseFee);
Wei newEffPriority =
newPendingTransaction.getTransaction().getEffectivePriorityFeePerGas(baseFee);
// bail early if price is not strictly positive
if (newEffPrice.equals(Wei.ZERO)) {
return false;
}
Wei curEffPrice = priceOf(existingTransactionInfo.getTransaction(), baseFee);
Wei curEffPrice = priceOf(existingPendingTransaction.getTransaction(), baseFee);
Wei curEffPriority =
existingTransactionInfo.getTransaction().getEffectivePriorityFeePerGas(baseFee);
existingPendingTransaction.getTransaction().getEffectivePriorityFeePerGas(baseFee);
if (isBumpedBy(curEffPrice, newEffPrice, priceBump)) {
// if effective price is bumped by percent:

@ -15,7 +15,6 @@
package org.hyperledger.besu.ethereum.eth.transactions;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionInfo;
import org.hyperledger.besu.util.number.Percentage;
import java.util.Optional;
@ -29,19 +28,19 @@ public class TransactionReplacementByGasPriceRule implements TransactionPoolRepl
@Override
public boolean shouldReplace(
final TransactionInfo existingTransactionInfo,
final TransactionInfo newTransactionInfo,
final PendingTransaction existingPendingTransaction,
final PendingTransaction newPendingTransaction,
final Optional<Wei> baseFee) {
assert existingTransactionInfo.getTransaction() != null
&& newTransactionInfo.getTransaction() != null;
assert existingPendingTransaction.getTransaction() != null
&& newPendingTransaction.getTransaction() != null;
// return false if either transaction supports 1559 fee market
if (isNotGasPriced(existingTransactionInfo) || isNotGasPriced(newTransactionInfo)) {
if (isNotGasPriced(existingPendingTransaction) || isNotGasPriced(newPendingTransaction)) {
return false;
}
final Wei replacementThreshold =
existingTransactionInfo.getGasPrice().multiply(100 + priceBump.getValue()).divide(100);
return newTransactionInfo.getGasPrice().compareTo(replacementThreshold) > 0;
existingPendingTransaction.getGasPrice().multiply(100 + priceBump.getValue()).divide(100);
return newPendingTransaction.getGasPrice().compareTo(replacementThreshold) > 0;
}
}

@ -14,25 +14,24 @@
*/
package org.hyperledger.besu.ethereum.eth.transactions.sorter;
import static org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionAddedStatus.ADDED;
import static org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionAddedStatus.LOWER_NONCE_INVALID_TRANSACTION_KNOWN;
import static org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionAddedStatus.REJECTED_UNDERPRICED_REPLACEMENT;
import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedStatus.ADDED;
import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedStatus.LOWER_NONCE_INVALID_TRANSACTION_KNOWN;
import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedStatus.REJECTED_UNDERPRICED_REPLACEMENT;
import static org.hyperledger.besu.util.Slf4jLambdaHelper.debugLambda;
import static org.hyperledger.besu.util.Slf4jLambdaHelper.traceLambda;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.AccountTransactionOrder;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction;
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactionDroppedListener;
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactionListener;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedStatus;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolReplacementHandler;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionsForSenderInfo;
import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason;
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.metrics.BesuMetricCategory;
import org.hyperledger.besu.plugin.services.MetricsSystem;
@ -43,7 +42,6 @@ import org.hyperledger.besu.util.Subscribers;
import java.time.Clock;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@ -55,10 +53,7 @@ import java.util.OptionalLong;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
@ -82,9 +77,9 @@ public abstract class AbstractPendingTransactionsSorter {
protected final TransactionPoolConfiguration poolConfig;
protected final Object lock = new Object();
protected final Map<Hash, TransactionInfo> pendingTransactions = new ConcurrentHashMap<>();
protected final Map<Hash, PendingTransaction> pendingTransactions = new ConcurrentHashMap<>();
protected final Map<Address, TransactionsForSenderInfo> transactionsBySender =
protected final Map<Address, PendingTransactionsForSender> transactionsBySender =
new ConcurrentHashMap<>();
protected final LowestInvalidNonceCache lowestInvalidKnownNonceCache =
@ -151,8 +146,8 @@ public abstract class AbstractPendingTransactionsSorter {
public List<Transaction> getLocalTransactions() {
return pendingTransactions.values().stream()
.filter(TransactionInfo::isReceivedFromLocalSource)
.map(TransactionInfo::getTransaction)
.filter(PendingTransaction::isReceivedFromLocalSource)
.map(PendingTransaction::getTransaction)
.collect(Collectors.toList());
}
@ -167,10 +162,10 @@ public abstract class AbstractPendingTransactionsSorter {
return LOWER_NONCE_INVALID_TRANSACTION_KNOWN;
}
final TransactionInfo transactionInfo =
new TransactionInfo(transaction, false, clock.instant());
final PendingTransaction pendingTransaction =
new PendingTransaction(transaction, false, clock.instant());
final TransactionAddedStatus transactionAddedStatus =
addTransaction(transactionInfo, maybeSenderAccount);
addTransaction(pendingTransaction, maybeSenderAccount);
if (transactionAddedStatus.equals(ADDED)) {
lowestInvalidKnownNonceCache.registerValidTransaction(transaction);
remoteTransactionAddedCounter.inc();
@ -182,7 +177,8 @@ public abstract class AbstractPendingTransactionsSorter {
public TransactionAddedStatus addLocalTransaction(
final Transaction transaction, final Optional<Account> maybeSenderAccount) {
final TransactionAddedStatus transactionAdded =
addTransaction(new TransactionInfo(transaction, true, clock.instant()), maybeSenderAccount);
addTransaction(
new PendingTransaction(transaction, true, clock.instant()), maybeSenderAccount);
if (transactionAdded.equals(ADDED)) {
localTransactionAddedCounter.inc();
}
@ -217,22 +213,23 @@ public abstract class AbstractPendingTransactionsSorter {
synchronized (lock) {
final Set<Transaction> transactionsToRemove = new HashSet<>();
final Map<Address, AccountTransactionOrder> accountTransactions = new HashMap<>();
final Iterator<TransactionInfo> prioritizedTransactions = prioritizedTransactions();
final Iterator<PendingTransaction> prioritizedTransactions = prioritizedTransactions();
while (prioritizedTransactions.hasNext()) {
final TransactionInfo highestPriorityTransactionInfo = prioritizedTransactions.next();
final PendingTransaction highestPriorityPendingTransaction = prioritizedTransactions.next();
final AccountTransactionOrder accountTransactionOrder =
accountTransactions.computeIfAbsent(
highestPriorityTransactionInfo.getSender(), this::createSenderTransactionOrder);
highestPriorityPendingTransaction.getSender(), this::createSenderTransactionOrder);
for (final Transaction transactionToProcess :
accountTransactionOrder.transactionsToProcess(
highestPriorityTransactionInfo.getTransaction())) {
highestPriorityPendingTransaction.getTransaction())) {
final TransactionSelectionResult result =
selector.evaluateTransaction(transactionToProcess);
switch (result) {
case DELETE_TRANSACTION_AND_CONTINUE:
transactionsToRemove.add(transactionToProcess);
signalInvalidTransaction(transactionToProcess).forEach(transactionsToRemove::add);
signalInvalidAndGetDependentTransactions(transactionToProcess)
.forEach(transactionsToRemove::add);
break;
case CONTINUE:
break;
@ -252,54 +249,54 @@ public abstract class AbstractPendingTransactionsSorter {
return new AccountTransactionOrder(
transactionsBySender
.get(address)
.streamTransactionInfos()
.map(TransactionInfo::getTransaction));
.streamPendingTransactions()
.map(PendingTransaction::getTransaction));
}
protected TransactionAddedStatus addTransactionForSenderAndNonce(
final TransactionInfo transactionInfo, final Optional<Account> maybeSenderAccount) {
final PendingTransaction pendingTransaction, final Optional<Account> maybeSenderAccount) {
TransactionsForSenderInfo txsSenderInfo =
PendingTransactionsForSender pendingTxsForSender =
transactionsBySender.computeIfAbsent(
transactionInfo.getSender(),
address -> new TransactionsForSenderInfo(maybeSenderAccount));
pendingTransaction.getSender(),
address -> new PendingTransactionsForSender(maybeSenderAccount));
TransactionInfo existingTxInfo =
txsSenderInfo.getTransactionInfoForNonce(transactionInfo.getNonce());
PendingTransaction existingPendingTx =
pendingTxsForSender.getPendingTransactionForNonce(pendingTransaction.getNonce());
final Optional<Transaction> maybeReplacedTransaction;
if (existingTxInfo != null) {
if (existingPendingTx != null) {
if (!transactionReplacementHandler.shouldReplace(
existingTxInfo, transactionInfo, chainHeadHeaderSupplier.get())) {
existingPendingTx, pendingTransaction, chainHeadHeaderSupplier.get())) {
traceLambda(
LOG, "Reject underpriced transaction replacement {}", transactionInfo::toTraceLog);
LOG, "Reject underpriced transaction replacement {}", pendingTransaction::toTraceLog);
return REJECTED_UNDERPRICED_REPLACEMENT;
}
traceLambda(
LOG,
"Replace existing transaction {}, with new transaction {}",
existingTxInfo::toTraceLog,
transactionInfo::toTraceLog);
maybeReplacedTransaction = Optional.of(existingTxInfo.getTransaction());
existingPendingTx::toTraceLog,
pendingTransaction::toTraceLog);
maybeReplacedTransaction = Optional.of(existingPendingTx.getTransaction());
} else {
maybeReplacedTransaction = Optional.empty();
}
txsSenderInfo.updateSenderAccount(maybeSenderAccount);
txsSenderInfo.addTransactionToTrack(transactionInfo);
traceLambda(LOG, "Tracked transaction by sender {}", txsSenderInfo::toTraceLog);
pendingTxsForSender.updateSenderAccount(maybeSenderAccount);
pendingTxsForSender.trackPendingTransaction(pendingTransaction);
traceLambda(LOG, "Tracked transaction by sender {}", pendingTxsForSender::toTraceLog);
maybeReplacedTransaction.ifPresent(this::removeTransaction);
return ADDED;
}
protected void removeTransactionInfoTrackedBySenderAndNonce(
final TransactionInfo transactionInfo) {
final Transaction transaction = transactionInfo.getTransaction();
protected void removePendingTransactionBySenderAndNonce(
final PendingTransaction pendingTransaction) {
final Transaction transaction = pendingTransaction.getTransaction();
Optional.ofNullable(transactionsBySender.get(transaction.getSender()))
.ifPresent(
transactionsForSender -> {
transactionsForSender.removeTrackedTransactionInfo(transactionInfo);
if (transactionsForSender.transactionCount() == 0) {
pendingTxsForSender -> {
pendingTxsForSender.removeTrackedPendingTransaction(pendingTransaction);
if (pendingTxsForSender.transactionCount() == 0) {
LOG.trace(
"Removing sender {} from transactionBySender since no more tracked transactions",
transaction.getSender());
@ -308,7 +305,7 @@ public abstract class AbstractPendingTransactionsSorter {
traceLambda(
LOG,
"Tracked transaction by sender {} after the removal of {}",
transactionsForSender::toTraceLog,
pendingTxsForSender::toTraceLog,
transaction::toTraceLog);
}
});
@ -336,10 +333,10 @@ public abstract class AbstractPendingTransactionsSorter {
public Optional<Transaction> getTransactionByHash(final Hash transactionHash) {
return Optional.ofNullable(pendingTransactions.get(transactionHash))
.map(TransactionInfo::getTransaction);
.map(PendingTransaction::getTransaction);
}
public Set<TransactionInfo> getTransactionInfo() {
public Set<PendingTransaction> getPendingTransactions() {
return new HashSet<>(pendingTransactions.values());
}
@ -360,10 +357,11 @@ public abstract class AbstractPendingTransactionsSorter {
}
public OptionalLong getNextNonceForSender(final Address sender) {
final TransactionsForSenderInfo transactionsForSenderInfo = transactionsBySender.get(sender);
return transactionsForSenderInfo == null
final PendingTransactionsForSender pendingTransactionsForSender =
transactionsBySender.get(sender);
return pendingTransactionsForSender == null
? OptionalLong.empty()
: transactionsForSenderInfo.maybeNextNonce();
: pendingTransactionsForSender.maybeNextNonce();
}
public abstract void manageBlockAdded(final Block block);
@ -371,19 +369,19 @@ public abstract class AbstractPendingTransactionsSorter {
protected abstract void doRemoveTransaction(
final Transaction transaction, final boolean addedToBlock);
protected abstract Iterator<TransactionInfo> prioritizedTransactions();
protected abstract Iterator<PendingTransaction> prioritizedTransactions();
protected abstract TransactionAddedStatus addTransaction(
final TransactionInfo transactionInfo, final Optional<Account> maybeSenderAccount);
final PendingTransaction pendingTransaction, final Optional<Account> maybeSenderAccount);
Optional<TransactionInfo> lowestValueTxForRemovalBySender(
final NavigableSet<TransactionInfo> txSet) {
Optional<PendingTransaction> lowestValueTxForRemovalBySender(
final NavigableSet<PendingTransaction> txSet) {
return txSet.descendingSet().stream()
.filter(
tx ->
transactionsBySender
.get(tx.getSender())
.maybeLastTx()
.maybeLastPendingTransaction()
.filter(tx::equals)
.isPresent())
.findFirst();
@ -400,17 +398,17 @@ public abstract class AbstractPendingTransactionsSorter {
prioritizedTransactions(), Spliterator.ORDERED),
false)
.map(
txInfo -> {
TransactionsForSenderInfo txsSenderInfo =
transactionsBySender.get(txInfo.getSender());
pendingTx -> {
PendingTransactionsForSender pendingTxsForSender =
transactionsBySender.get(pendingTx.getSender());
long nonceDistance =
txInfo.getNonce() - txsSenderInfo.getSenderAccountNonce();
pendingTx.getNonce() - pendingTxsForSender.getSenderAccountNonce();
return "nonceDistance: "
+ nonceDistance
+ ", senderAccount: "
+ txsSenderInfo.getSenderAccount()
+ pendingTxsForSender.getSenderAccount()
+ ", "
+ txInfo.toTraceLog();
+ pendingTx.toTraceLog();
})
.collect(Collectors.joining("; "))
+ " }");
@ -433,118 +431,27 @@ public abstract class AbstractPendingTransactionsSorter {
}
}
public List<Transaction> signalInvalidTransaction(final Transaction transaction) {
public List<Transaction> signalInvalidAndGetDependentTransactions(final Transaction transaction) {
final long invalidNonce = lowestInvalidKnownNonceCache.registerInvalidTransaction(transaction);
TransactionsForSenderInfo txsForSender = transactionsBySender.get(transaction.getSender());
PendingTransactionsForSender txsForSender = transactionsBySender.get(transaction.getSender());
if (txsForSender != null) {
return txsForSender
.streamTransactionInfos()
.filter(txInfo -> txInfo.getTransaction().getNonce() > invalidNonce)
.streamPendingTransactions()
.filter(pendingTx -> pendingTx.getTransaction().getNonce() > invalidNonce)
.peek(
txInfo ->
pendingTx ->
traceLambda(
LOG,
"Transaction {} piked for removal since there is a lowest invalid nonce {} for the sender",
txInfo::toTraceLog,
pendingTx::toTraceLog,
() -> invalidNonce))
.map(TransactionInfo::getTransaction)
.map(PendingTransaction::getTransaction)
.collect(Collectors.toList());
}
return List.of();
}
/**
* Tracks the additional metadata associated with transactions to enable prioritization for mining
* and deciding which transactions to drop when the transaction pool reaches its size limit.
*/
public static class TransactionInfo {
private static final AtomicLong TRANSACTIONS_ADDED = new AtomicLong();
private final Transaction transaction;
private final boolean receivedFromLocalSource;
private final Instant addedToPoolAt;
private final long sequence; // Allows prioritization based on order transactions are added
public TransactionInfo(
final Transaction transaction,
final boolean receivedFromLocalSource,
final Instant addedToPoolAt) {
this.transaction = transaction;
this.receivedFromLocalSource = receivedFromLocalSource;
this.addedToPoolAt = addedToPoolAt;
this.sequence = TRANSACTIONS_ADDED.getAndIncrement();
}
public Transaction getTransaction() {
return transaction;
}
public Wei getGasPrice() {
return transaction.getGasPrice().orElse(Wei.ZERO);
}
public long getSequence() {
return sequence;
}
public long getNonce() {
return transaction.getNonce();
}
public Address getSender() {
return transaction.getSender();
}
public boolean isReceivedFromLocalSource() {
return receivedFromLocalSource;
}
public Hash getHash() {
return transaction.getHash();
}
public Instant getAddedToPoolAt() {
return addedToPoolAt;
}
public static List<Transaction> toTransactionList(
final Collection<TransactionInfo> transactionsInfo) {
return transactionsInfo.stream()
.map(TransactionInfo::getTransaction)
.collect(Collectors.toUnmodifiableList());
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TransactionInfo that = (TransactionInfo) o;
return sequence == that.sequence;
}
@Override
public int hashCode() {
return 31 * (int) (sequence ^ (sequence >>> 32));
}
public String toTraceLog() {
return "{sequence: "
+ sequence
+ ", addedAt: "
+ addedToPoolAt
+ ", "
+ transaction.toTraceLog()
+ "}";
}
}
public enum TransactionSelectionResult {
DELETE_TRANSACTION_AND_CONTINUE,
CONTINUE,
@ -556,220 +463,4 @@ public abstract class AbstractPendingTransactionsSorter {
TransactionSelectionResult evaluateTransaction(final Transaction transaction);
}
public enum TransactionAddedStatus {
ALREADY_KNOWN(TransactionInvalidReason.TRANSACTION_ALREADY_KNOWN),
REJECTED_UNDERPRICED_REPLACEMENT(TransactionInvalidReason.TRANSACTION_REPLACEMENT_UNDERPRICED),
NONCE_TOO_FAR_IN_FUTURE_FOR_SENDER(TransactionInvalidReason.NONCE_TOO_FAR_IN_FUTURE_FOR_SENDER),
LOWER_NONCE_INVALID_TRANSACTION_KNOWN(
TransactionInvalidReason.LOWER_NONCE_INVALID_TRANSACTION_EXISTS),
ADDED();
private final Optional<TransactionInvalidReason> invalidReason;
TransactionAddedStatus() {
this.invalidReason = Optional.empty();
}
TransactionAddedStatus(final TransactionInvalidReason invalidReason) {
this.invalidReason = Optional.of(invalidReason);
}
public Optional<TransactionInvalidReason> getInvalidReason() {
return invalidReason;
}
}
private static class LowestInvalidNonceCache {
private final int maxSize;
private final Map<Address, InvalidNonceStatus> lowestInvalidKnownNonceBySender;
private final NavigableSet<InvalidNonceStatus> evictionOrder = new TreeSet<>();
public LowestInvalidNonceCache(final int maxSize) {
this.maxSize = maxSize;
this.lowestInvalidKnownNonceBySender = new HashMap<>(maxSize);
}
synchronized long registerInvalidTransaction(final Transaction transaction) {
final Address sender = transaction.getSender();
final long invalidNonce = transaction.getNonce();
final InvalidNonceStatus currStatus = lowestInvalidKnownNonceBySender.get(sender);
if (currStatus == null) {
final InvalidNonceStatus newStatus = new InvalidNonceStatus(sender, invalidNonce);
addInvalidNonceStatus(newStatus);
traceLambda(
LOG,
"Added invalid nonce status {}, cache status {}",
newStatus::toString,
this::toString);
return invalidNonce;
}
updateInvalidNonceStatus(
currStatus,
status -> {
if (invalidNonce < currStatus.nonce) {
currStatus.updateNonce(invalidNonce);
} else {
currStatus.newHit();
}
});
traceLambda(
LOG,
"Updated invalid nonce status {}, cache status {}",
currStatus::toString,
this::toString);
return currStatus.nonce;
}
synchronized void registerValidTransaction(final Transaction transaction) {
final InvalidNonceStatus currStatus =
lowestInvalidKnownNonceBySender.get(transaction.getSender());
if (currStatus != null) {
evictionOrder.remove(currStatus);
lowestInvalidKnownNonceBySender.remove(transaction.getSender());
traceLambda(
LOG,
"Valid transaction, removed invalid nonce status {}, cache status {}",
currStatus::toString,
this::toString);
}
}
synchronized boolean hasInvalidLowerNonce(final Transaction transaction) {
final InvalidNonceStatus currStatus =
lowestInvalidKnownNonceBySender.get(transaction.getSender());
if (currStatus != null && transaction.getNonce() > currStatus.nonce) {
updateInvalidNonceStatus(currStatus, status -> status.newHit());
traceLambda(
LOG,
"New hit for invalid nonce status {}, cache status {}",
currStatus::toString,
this::toString);
return true;
}
return false;
}
private void updateInvalidNonceStatus(
final InvalidNonceStatus status, final Consumer<InvalidNonceStatus> updateAction) {
evictionOrder.remove(status);
updateAction.accept(status);
evictionOrder.add(status);
}
private void addInvalidNonceStatus(final InvalidNonceStatus newStatus) {
if (lowestInvalidKnownNonceBySender.size() >= maxSize) {
final InvalidNonceStatus statusToEvict = evictionOrder.pollFirst();
lowestInvalidKnownNonceBySender.remove(statusToEvict.address);
traceLambda(
LOG,
"Evicted invalid nonce status {}, cache status {}",
statusToEvict::toString,
this::toString);
}
lowestInvalidKnownNonceBySender.put(newStatus.address, newStatus);
evictionOrder.add(newStatus);
}
synchronized String toTraceLog() {
return "by eviction order "
+ StreamSupport.stream(evictionOrder.spliterator(), false)
.map(InvalidNonceStatus::toString)
.collect(Collectors.joining("; "));
}
@Override
public String toString() {
return "LowestInvalidNonceCache{"
+ "maxSize: "
+ maxSize
+ ", currentSize: "
+ lowestInvalidKnownNonceBySender.size()
+ ", evictionOrder: [size: "
+ evictionOrder.size()
+ ", first evictable: "
+ evictionOrder.first()
+ "]"
+ '}';
}
private static class InvalidNonceStatus implements Comparable<InvalidNonceStatus> {
final Address address;
long nonce;
long hits;
long lastUpdate;
InvalidNonceStatus(final Address address, final long nonce) {
this.address = address;
this.nonce = nonce;
this.hits = 1L;
this.lastUpdate = System.currentTimeMillis();
}
void updateNonce(final long nonce) {
this.nonce = nonce;
newHit();
}
void newHit() {
this.hits++;
this.lastUpdate = System.currentTimeMillis();
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
InvalidNonceStatus that = (InvalidNonceStatus) o;
return address.equals(that.address);
}
@Override
public int hashCode() {
return address.hashCode();
}
/**
* An InvalidNonceStatus is smaller than another when it has fewer hits and was last access
* earlier, the address is the last tiebreaker
*
* @param o the object to be compared.
* @return 0 if they are equal, negative if this is smaller, positive if this is greater
*/
@Override
public int compareTo(final InvalidNonceStatus o) {
final int cmpHits = Long.compare(this.hits, o.hits);
if (cmpHits != 0) {
return cmpHits;
}
final int cmpLastUpdate = Long.compare(this.lastUpdate, o.lastUpdate);
if (cmpLastUpdate != 0) {
return cmpLastUpdate;
}
return this.address.compareTo(o.address);
}
@Override
public String toString() {
return "{"
+ "address="
+ address
+ ", nonce="
+ nonce
+ ", hits="
+ hits
+ ", lastUpdate="
+ lastUpdate
+ '}';
}
}
}
}

@ -16,32 +16,30 @@ package org.hyperledger.besu.ethereum.eth.transactions.sorter;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toUnmodifiableList;
import static org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionAddedStatus.ADDED;
import static org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionAddedStatus.ALREADY_KNOWN;
import static org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionAddedStatus.NONCE_TOO_FAR_IN_FUTURE_FOR_SENDER;
import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedStatus.ADDED;
import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedStatus.ALREADY_KNOWN;
import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedStatus.NONCE_TOO_FAR_IN_FUTURE_FOR_SENDER;
import static org.hyperledger.besu.util.Slf4jLambdaHelper.traceLambda;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedStatus;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionsForSenderInfo;
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.evm.account.AccountState;
import org.hyperledger.besu.plugin.services.MetricsSystem;
import java.time.Clock;
import java.util.Comparator;
import java.util.Iterator;
import java.util.NavigableSet;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.TreeSet;
import java.util.function.Supplier;
import java.util.stream.Stream;
import com.google.errorprone.annotations.Keep;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -70,41 +68,41 @@ public class BaseFeePendingTransactionsSorter extends AbstractPendingTransaction
* See this post for an explainer about these data structures:
* https://hackmd.io/@adietrichs/1559-transaction-sorting
*/
private final NavigableSet<TransactionInfo> prioritizedTransactionsStaticRange =
private final NavigableSet<PendingTransaction> prioritizedTransactionsStaticRange =
new TreeSet<>(
comparing(TransactionInfo::isReceivedFromLocalSource)
comparing(PendingTransaction::isReceivedFromLocalSource)
.thenComparing(
transactionInfo ->
transactionInfo
pendingTx ->
pendingTx
.getTransaction()
.getMaxPriorityFeePerGas()
// just in case we attempt to compare non-1559 transaction
.orElse(Wei.ZERO)
.getAsBigInteger()
.longValue())
.thenComparing(TransactionInfo::getAddedToPoolAt)
.thenComparing(TransactionInfo::getSequence)
.thenComparing(PendingTransaction::getAddedToPoolAt)
.thenComparing(PendingTransaction::getSequence)
.reversed());
private final NavigableSet<TransactionInfo> prioritizedTransactionsDynamicRange =
private final NavigableSet<PendingTransaction> prioritizedTransactionsDynamicRange =
new TreeSet<>(
comparing(TransactionInfo::isReceivedFromLocalSource)
comparing(PendingTransaction::isReceivedFromLocalSource)
.thenComparing(
transactionInfo ->
transactionInfo
pendingTx ->
pendingTx
.getTransaction()
.getMaxFeePerGas()
.map(maxFeePerGas -> maxFeePerGas.getAsBigInteger().longValue())
.orElse(transactionInfo.getGasPrice().toLong()))
.thenComparing(TransactionInfo::getAddedToPoolAt)
.thenComparing(TransactionInfo::getSequence)
.orElse(pendingTx.getGasPrice().toLong()))
.thenComparing(PendingTransaction::getAddedToPoolAt)
.thenComparing(PendingTransaction::getSequence)
.reversed());
private final TreeSet<TransactionInfo> transactionsByEvictionOrder =
private final TreeSet<PendingTransaction> transactionsByEvictionOrder =
new TreeSet<>(
comparing(TransactionInfo::isReceivedFromLocalSource)
comparing(PendingTransaction::isReceivedFromLocalSource)
.reversed()
.thenComparing(TransactionInfo::getSequence));
.thenComparing(PendingTransaction::getSequence));
@Override
public void manageBlockAdded(final Block block) {
@ -114,45 +112,41 @@ public class BaseFeePendingTransactionsSorter extends AbstractPendingTransaction
@Override
protected void doRemoveTransaction(final Transaction transaction, final boolean addedToBlock) {
synchronized (lock) {
final TransactionInfo removedTransactionInfo =
pendingTransactions.remove(transaction.getHash());
if (removedTransactionInfo != null) {
transactionsByEvictionOrder.remove(removedTransactionInfo);
if (prioritizedTransactionsDynamicRange.remove(removedTransactionInfo)) {
traceLambda(
LOG, "Removed dynamic range transaction {}", removedTransactionInfo::toTraceLog);
final PendingTransaction removedPendingTx = pendingTransactions.remove(transaction.getHash());
if (removedPendingTx != null) {
transactionsByEvictionOrder.remove(removedPendingTx);
if (prioritizedTransactionsDynamicRange.remove(removedPendingTx)) {
traceLambda(LOG, "Removed dynamic range transaction {}", removedPendingTx::toTraceLog);
} else {
removedTransactionInfo
removedPendingTx
.getTransaction()
.getMaxPriorityFeePerGas()
.ifPresent(
__ -> {
if (prioritizedTransactionsStaticRange.remove(removedTransactionInfo)) {
if (prioritizedTransactionsStaticRange.remove(removedPendingTx)) {
traceLambda(
LOG,
"Removed static range transaction {}",
removedTransactionInfo::toTraceLog);
LOG, "Removed static range transaction {}", removedPendingTx::toTraceLog);
}
});
}
removeTransactionInfoTrackedBySenderAndNonce(removedTransactionInfo);
removePendingTransactionBySenderAndNonce(removedPendingTx);
incrementTransactionRemovedCounter(
removedTransactionInfo.isReceivedFromLocalSource(), addedToBlock);
removedPendingTx.isReceivedFromLocalSource(), addedToBlock);
}
}
}
@Override
protected Iterator<TransactionInfo> prioritizedTransactions() {
protected Iterator<PendingTransaction> prioritizedTransactions() {
return new Iterator<>() {
final Iterator<TransactionInfo> staticRangeIterable =
final Iterator<PendingTransaction> staticRangeIterable =
prioritizedTransactionsStaticRange.iterator();
final Iterator<TransactionInfo> dynamicRangeIterable =
final Iterator<PendingTransaction> dynamicRangeIterable =
prioritizedTransactionsDynamicRange.iterator();
Optional<TransactionInfo> currentStaticRangeTransaction =
Optional<PendingTransaction> currentStaticRangeTransaction =
getNextOptional(staticRangeIterable);
Optional<TransactionInfo> currentDynamicRangeTransaction =
Optional<PendingTransaction> currentDynamicRangeTransaction =
getNextOptional(dynamicRangeIterable);
@Override
@ -162,17 +156,17 @@ public class BaseFeePendingTransactionsSorter extends AbstractPendingTransaction
}
@Override
public TransactionInfo next() {
public PendingTransaction next() {
if (currentStaticRangeTransaction.isEmpty() && currentDynamicRangeTransaction.isEmpty()) {
throw new NoSuchElementException("Tried to iterate past end of iterator.");
} else if (currentStaticRangeTransaction.isEmpty()) {
// only dynamic range txs left
final TransactionInfo best = currentDynamicRangeTransaction.get();
final PendingTransaction best = currentDynamicRangeTransaction.get();
currentDynamicRangeTransaction = getNextOptional(dynamicRangeIterable);
return best;
} else if (currentDynamicRangeTransaction.isEmpty()) {
// only static range txs left
final TransactionInfo best = currentStaticRangeTransaction.get();
final PendingTransaction best = currentStaticRangeTransaction.get();
currentStaticRangeTransaction = getNextOptional(staticRangeIterable);
return best;
} else {
@ -188,7 +182,7 @@ public class BaseFeePendingTransactionsSorter extends AbstractPendingTransaction
.get()
.getTransaction()
.getEffectivePriorityFeePerGas(baseFee);
final TransactionInfo best;
final PendingTransaction best;
if (dynamicRangeEffectivePriorityFee.compareTo(staticRangeEffectivePriorityFee) > 0) {
best = currentDynamicRangeTransaction.get();
currentDynamicRangeTransaction = getNextOptional(dynamicRangeIterable);
@ -200,10 +194,10 @@ public class BaseFeePendingTransactionsSorter extends AbstractPendingTransaction
}
}
private Optional<TransactionInfo> getNextOptional(
final Iterator<TransactionInfo> transactionInfoIterator) {
return transactionInfoIterator.hasNext()
? Optional.of(transactionInfoIterator.next())
private Optional<PendingTransaction> getNextOptional(
final Iterator<PendingTransaction> pendingTxsIterator) {
return pendingTxsIterator.hasNext()
? Optional.of(pendingTxsIterator.next())
: Optional.empty();
}
};
@ -211,12 +205,12 @@ public class BaseFeePendingTransactionsSorter extends AbstractPendingTransaction
@Override
protected TransactionAddedStatus addTransaction(
final TransactionInfo transactionInfo, final Optional<Account> maybeSenderAccount) {
final PendingTransaction pendingTransaction, final Optional<Account> maybeSenderAccount) {
Optional<Transaction> droppedTransaction = Optional.empty();
final Transaction transaction = transactionInfo.getTransaction();
final Transaction transaction = pendingTransaction.getTransaction();
synchronized (lock) {
if (pendingTransactions.containsKey(transactionInfo.getHash())) {
traceLambda(LOG, "Already known transaction {}", transactionInfo::toTraceLog);
if (pendingTransactions.containsKey(pendingTransaction.getHash())) {
traceLambda(LOG, "Already known transaction {}", pendingTransaction::toTraceLog);
return ALREADY_KNOWN;
}
@ -231,13 +225,13 @@ public class BaseFeePendingTransactionsSorter extends AbstractPendingTransaction
}
final TransactionAddedStatus transactionAddedStatus =
addTransactionForSenderAndNonce(transactionInfo, maybeSenderAccount);
addTransactionForSenderAndNonce(pendingTransaction, maybeSenderAccount);
if (!transactionAddedStatus.equals(ADDED)) {
traceLambda(
LOG,
"Not added with status {}, transaction {}",
transactionAddedStatus::name,
transactionInfo::toTraceLog);
pendingTransaction::toTraceLog);
return transactionAddedStatus;
}
@ -245,18 +239,18 @@ public class BaseFeePendingTransactionsSorter extends AbstractPendingTransaction
final String kind;
if (isInStaticRange(transaction, baseFee)) {
kind = "static";
prioritizedTransactionsStaticRange.add(transactionInfo);
prioritizedTransactionsStaticRange.add(pendingTransaction);
} else {
kind = "dynamic";
prioritizedTransactionsDynamicRange.add(transactionInfo);
prioritizedTransactionsDynamicRange.add(pendingTransaction);
}
traceLambda(
LOG,
"Adding {} to pending transactions, range type {}",
transactionInfo::toTraceLog,
pendingTransaction::toTraceLog,
kind::toString);
pendingTransactions.put(transactionInfo.getHash(), transactionInfo);
transactionsByEvictionOrder.add(transactionInfo);
pendingTransactions.put(pendingTransaction.getHash(), pendingTransaction);
transactionsByEvictionOrder.add(pendingTransaction);
// if we are over txpool limit, select a transaction to evict
if (pendingTransactions.size() > poolConfig.getTxPoolMaxSize()) {
@ -285,62 +279,17 @@ public class BaseFeePendingTransactionsSorter extends AbstractPendingTransaction
private Optional<Transaction> getTransactionToEvict() {
// select transaction to drop by lowest sequence and then by max nonce for the sender
final TransactionInfo firstTransactionInfo = transactionsByEvictionOrder.first();
final TransactionsForSenderInfo transactionsForSenderInfo =
transactionsBySender.get(firstTransactionInfo.getSender());
final PendingTransaction firstPendingTx = transactionsByEvictionOrder.first();
final PendingTransactionsForSender pendingTxsForSender =
transactionsBySender.get(firstPendingTx.getSender());
traceLambda(
LOG,
"Oldest transaction info {} will pick transaction with highest nonce for that sender {}",
firstTransactionInfo::toTraceLog,
transactionsForSenderInfo::toTraceLog);
return transactionsForSenderInfo.maybeLastTx().map(TransactionInfo::getTransaction);
}
@Keep
private Optional<Transaction> selectLowestValueTransaction() {
Optional<Transaction> droppedTransaction;
final Stream.Builder<TransactionInfo> removalCandidates = Stream.builder();
if (!prioritizedTransactionsDynamicRange.isEmpty())
lowestValueTxForRemovalBySender(prioritizedTransactionsDynamicRange)
.ifPresent(
tx -> {
traceLambda(
LOG,
"Selected for removal dynamic range transaction {} effective price {}",
tx::toTraceLog,
() ->
tx.getTransaction()
.getEffectivePriorityFeePerGas(baseFee)
.getAsBigInteger());
removalCandidates.add(tx);
});
if (!prioritizedTransactionsStaticRange.isEmpty())
lowestValueTxForRemovalBySender(prioritizedTransactionsStaticRange)
.ifPresent(
tx -> {
traceLambda(
LOG,
"Selected for removal static range transaction {} effective price {}",
tx::toTraceLog,
() ->
tx.getTransaction()
.getEffectivePriorityFeePerGas(baseFee)
.getAsBigInteger());
removalCandidates.add(tx);
});
droppedTransaction =
removalCandidates
.build()
.min(
Comparator.comparing(
txInfo ->
txInfo
.getTransaction()
.getEffectivePriorityFeePerGas(baseFee)
.getAsBigInteger()))
.map(TransactionInfo::getTransaction);
return droppedTransaction;
firstPendingTx::toTraceLog,
pendingTxsForSender::toTraceLog);
return pendingTxsForSender
.maybeLastPendingTransaction()
.map(PendingTransaction::getTransaction);
}
private boolean isInStaticRange(final Transaction transaction, final Optional<Wei> baseFee) {
@ -373,16 +322,16 @@ public class BaseFeePendingTransactionsSorter extends AbstractPendingTransaction
.filter(
// these are the transactions whose effective priority fee have now dropped
// below their max priority fee
transactionInfo1 -> !isInStaticRange(transactionInfo1.getTransaction(), baseFee))
pendingTx -> !isInStaticRange(pendingTx.getTransaction(), baseFee))
.collect(toUnmodifiableList())
.forEach(
transactionInfo -> {
pendingTx -> {
traceLambda(
LOG,
"Moving {} from static to dynamic gas fee paradigm",
transactionInfo::toTraceLog);
prioritizedTransactionsStaticRange.remove(transactionInfo);
prioritizedTransactionsDynamicRange.add(transactionInfo);
pendingTx::toTraceLog);
prioritizedTransactionsStaticRange.remove(pendingTx);
prioritizedTransactionsDynamicRange.add(pendingTx);
});
} else {
// base fee decreases can only cause transactions to go from dynamic to static range
@ -390,16 +339,16 @@ public class BaseFeePendingTransactionsSorter extends AbstractPendingTransaction
.filter(
// these are the transactions whose effective priority fee are now above their
// max priority fee
transactionInfo1 -> isInStaticRange(transactionInfo1.getTransaction(), baseFee))
pendingTx -> isInStaticRange(pendingTx.getTransaction(), baseFee))
.collect(toUnmodifiableList())
.forEach(
transactionInfo -> {
pendingTx -> {
traceLambda(
LOG,
"Moving {} from dynamic to static gas fee paradigm",
transactionInfo::toTraceLog);
prioritizedTransactionsDynamicRange.remove(transactionInfo);
prioritizedTransactionsStaticRange.add(transactionInfo);
pendingTx::toTraceLog);
prioritizedTransactionsDynamicRange.remove(pendingTx);
prioritizedTransactionsStaticRange.add(pendingTx);
});
}
}

@ -19,6 +19,8 @@ import static java.util.Comparator.comparing;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedStatus;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration;
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.plugin.services.MetricsSystem;
@ -43,12 +45,12 @@ public class GasPricePendingTransactionsSorter extends AbstractPendingTransactio
private static final Logger LOG =
LoggerFactory.getLogger(GasPricePendingTransactionsSorter.class);
private final NavigableSet<TransactionInfo> prioritizedTransactions =
private final NavigableSet<PendingTransaction> prioritizedTransactions =
new TreeSet<>(
comparing(TransactionInfo::isReceivedFromLocalSource)
.thenComparing(TransactionInfo::getGasPrice)
.thenComparing(TransactionInfo::getAddedToPoolAt)
.thenComparing(TransactionInfo::getSequence)
comparing(PendingTransaction::isReceivedFromLocalSource)
.thenComparing(PendingTransaction::getGasPrice)
.thenComparing(PendingTransaction::getAddedToPoolAt)
.thenComparing(PendingTransaction::getSequence)
.reversed());
public GasPricePendingTransactionsSorter(
@ -67,43 +69,46 @@ public class GasPricePendingTransactionsSorter extends AbstractPendingTransactio
@Override
protected void doRemoveTransaction(final Transaction transaction, final boolean addedToBlock) {
synchronized (lock) {
final TransactionInfo removedTransactionInfo =
pendingTransactions.remove(transaction.getHash());
if (removedTransactionInfo != null) {
prioritizedTransactions.remove(removedTransactionInfo);
removeTransactionInfoTrackedBySenderAndNonce(removedTransactionInfo);
final PendingTransaction removedPendingTx = pendingTransactions.remove(transaction.getHash());
if (removedPendingTx != null) {
prioritizedTransactions.remove(removedPendingTx);
removePendingTransactionBySenderAndNonce(removedPendingTx);
incrementTransactionRemovedCounter(
removedTransactionInfo.isReceivedFromLocalSource(), addedToBlock);
removedPendingTx.isReceivedFromLocalSource(), addedToBlock);
}
}
}
@Override
protected Iterator<TransactionInfo> prioritizedTransactions() {
protected Iterator<PendingTransaction> prioritizedTransactions() {
return prioritizedTransactions.iterator();
}
@Override
protected TransactionAddedStatus addTransaction(
final TransactionInfo transactionInfo, final Optional<Account> maybeSenderAccount) {
final PendingTransaction pendingTransaction, final Optional<Account> maybeSenderAccount) {
Optional<Transaction> droppedTransaction = Optional.empty();
synchronized (lock) {
if (pendingTransactions.containsKey(transactionInfo.getHash())) {
if (pendingTransactions.containsKey(pendingTransaction.getHash())) {
return TransactionAddedStatus.ALREADY_KNOWN;
}
final TransactionAddedStatus transactionAddedStatus =
addTransactionForSenderAndNonce(transactionInfo, maybeSenderAccount);
addTransactionForSenderAndNonce(pendingTransaction, maybeSenderAccount);
if (!transactionAddedStatus.equals(TransactionAddedStatus.ADDED)) {
return transactionAddedStatus;
}
prioritizedTransactions.add(transactionInfo);
pendingTransactions.put(transactionInfo.getHash(), transactionInfo);
prioritizedTransactions.add(pendingTransaction);
pendingTransactions.put(pendingTransaction.getHash(), pendingTransaction);
// check if this sender exceeds the transactions by sender limit:
var senderTxInfos = transactionsBySender.get(transactionInfo.getSender());
if (senderTxInfos.transactionCount() > poolConfig.getTxPoolMaxFutureTransactionByAccount()) {
droppedTransaction = senderTxInfos.maybeLastTx().map(TransactionInfo::getTransaction);
var pendingTxsForSender = transactionsBySender.get(pendingTransaction.getSender());
if (pendingTxsForSender.transactionCount()
> poolConfig.getTxPoolMaxFutureTransactionByAccount()) {
droppedTransaction =
pendingTxsForSender
.maybeLastPendingTransaction()
.map(PendingTransaction::getTransaction);
droppedTransaction.ifPresent(
tx -> LOG.trace("Evicted {} due to too many transactions from sender", tx));
} else {
@ -111,12 +116,12 @@ public class GasPricePendingTransactionsSorter extends AbstractPendingTransactio
if (pendingTransactions.size() > poolConfig.getTxPoolMaxSize()) {
droppedTransaction =
lowestValueTxForRemovalBySender(prioritizedTransactions)
.map(TransactionInfo::getTransaction);
.map(PendingTransaction::getTransaction);
}
}
droppedTransaction.ifPresent(tx -> doRemoveTransaction(tx, false));
}
notifyTransactionAdded(transactionInfo.getTransaction());
notifyTransactionAdded(pendingTransaction.getTransaction());
droppedTransaction.ifPresent(this::notifyTransactionDropped);
return TransactionAddedStatus.ADDED;
}

@ -0,0 +1,206 @@
/*
* Copyright Besu contributors.
*
* 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.transactions.sorter;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.ethereum.core.Transaction;
import java.util.HashMap;
import java.util.Map;
import java.util.NavigableSet;
import java.util.TreeSet;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class LowestInvalidNonceCache {
private static final Logger LOG = LoggerFactory.getLogger(LowestInvalidNonceCache.class);
private final int maxSize;
private final Map<Address, InvalidNonceStatus> lowestInvalidKnownNonceBySender;
private final NavigableSet<InvalidNonceStatus> evictionOrder = new TreeSet<>();
public LowestInvalidNonceCache(final int maxSize) {
this.maxSize = maxSize;
this.lowestInvalidKnownNonceBySender = new HashMap<>(maxSize);
}
synchronized long registerInvalidTransaction(final Transaction transaction) {
final Address sender = transaction.getSender();
final long invalidNonce = transaction.getNonce();
final InvalidNonceStatus currStatus = lowestInvalidKnownNonceBySender.get(sender);
if (currStatus == null) {
final InvalidNonceStatus newStatus = new InvalidNonceStatus(sender, invalidNonce);
addInvalidNonceStatus(newStatus);
LOG.trace("Added invalid nonce status {}, cache status {}", newStatus, this);
return invalidNonce;
}
updateInvalidNonceStatus(
currStatus,
status -> {
if (invalidNonce < currStatus.nonce) {
currStatus.updateNonce(invalidNonce);
} else {
currStatus.newHit();
}
});
LOG.trace("Updated invalid nonce status {}, cache status {}", currStatus, this);
return currStatus.nonce;
}
synchronized void registerValidTransaction(final Transaction transaction) {
final InvalidNonceStatus currStatus =
lowestInvalidKnownNonceBySender.get(transaction.getSender());
if (currStatus != null) {
evictionOrder.remove(currStatus);
lowestInvalidKnownNonceBySender.remove(transaction.getSender());
LOG.trace(
"Valid transaction, removed invalid nonce status {}, cache status {}", currStatus, this);
}
}
synchronized boolean hasInvalidLowerNonce(final Transaction transaction) {
final InvalidNonceStatus currStatus =
lowestInvalidKnownNonceBySender.get(transaction.getSender());
if (currStatus != null && transaction.getNonce() > currStatus.nonce) {
updateInvalidNonceStatus(currStatus, status -> status.newHit());
LOG.trace("New hit for invalid nonce status {}, cache status {}", currStatus, this);
return true;
}
return false;
}
private void updateInvalidNonceStatus(
final InvalidNonceStatus status, final Consumer<InvalidNonceStatus> updateAction) {
evictionOrder.remove(status);
updateAction.accept(status);
evictionOrder.add(status);
}
private void addInvalidNonceStatus(final InvalidNonceStatus newStatus) {
if (lowestInvalidKnownNonceBySender.size() >= maxSize) {
final InvalidNonceStatus statusToEvict = evictionOrder.pollFirst();
lowestInvalidKnownNonceBySender.remove(statusToEvict.address);
LOG.trace("Evicted invalid nonce status {}, cache status {}", statusToEvict, this);
}
lowestInvalidKnownNonceBySender.put(newStatus.address, newStatus);
evictionOrder.add(newStatus);
}
synchronized String toTraceLog() {
return "by eviction order "
+ StreamSupport.stream(evictionOrder.spliterator(), false)
.map(InvalidNonceStatus::toString)
.collect(Collectors.joining("; "));
}
@Override
public String toString() {
return "LowestInvalidNonceCache{"
+ "maxSize: "
+ maxSize
+ ", currentSize: "
+ lowestInvalidKnownNonceBySender.size()
+ ", evictionOrder: [size: "
+ evictionOrder.size()
+ ", first evictable: "
+ evictionOrder.first()
+ "]"
+ '}';
}
private static class InvalidNonceStatus implements Comparable<InvalidNonceStatus> {
final Address address;
long nonce;
long hits;
long lastUpdate;
InvalidNonceStatus(final Address address, final long nonce) {
this.address = address;
this.nonce = nonce;
this.hits = 1L;
this.lastUpdate = System.currentTimeMillis();
}
void updateNonce(final long nonce) {
this.nonce = nonce;
newHit();
}
void newHit() {
this.hits++;
this.lastUpdate = System.currentTimeMillis();
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
InvalidNonceStatus that = (InvalidNonceStatus) o;
return address.equals(that.address);
}
@Override
public int hashCode() {
return address.hashCode();
}
/**
* An InvalidNonceStatus is smaller than another when it has fewer hits and was last access
* earlier, the address is the last tiebreaker
*
* @param o the object to be compared.
* @return 0 if they are equal, negative if this is smaller, positive if this is greater
*/
@Override
public int compareTo(final InvalidNonceStatus o) {
final int cmpHits = Long.compare(this.hits, o.hits);
if (cmpHits != 0) {
return cmpHits;
}
final int cmpLastUpdate = Long.compare(this.lastUpdate, o.lastUpdate);
if (cmpLastUpdate != 0) {
return cmpLastUpdate;
}
return this.address.compareTo(o.address);
}
@Override
public String toString() {
return "{"
+ "address="
+ address
+ ", nonce="
+ nonce
+ ", hits="
+ hits
+ ", lastUpdate="
+ lastUpdate
+ '}';
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright ConsenSys AG.
* Copyright Besu contributors.
*
* 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
@ -12,10 +12,9 @@
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.eth.transactions.sorter;
package org.hyperledger.besu.ethereum.eth.transactions;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionInfo;
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction;
import org.hyperledger.besu.evm.account.Account;
import java.util.Map;
@ -26,38 +25,39 @@ import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class TransactionsForSenderInfo {
private final NavigableMap<Long, TransactionInfo> transactionsInfos;
public class PendingTransactionsForSender {
private final NavigableMap<Long, PendingTransaction> pendingTransactions;
private OptionalLong nextGap = OptionalLong.empty();
private Optional<Account> maybeSenderAccount;
public TransactionsForSenderInfo(final Optional<Account> maybeSenderAccount) {
this.transactionsInfos = new TreeMap<>();
public PendingTransactionsForSender(final Optional<Account> maybeSenderAccount) {
this.pendingTransactions = new TreeMap<>();
this.maybeSenderAccount = maybeSenderAccount;
}
public void addTransactionToTrack(final TransactionInfo transactionInfo) {
final long nonce = transactionInfo.getNonce();
synchronized (transactionsInfos) {
if (!transactionsInfos.isEmpty()) {
final long expectedNext = transactionsInfos.lastKey() + 1;
public void trackPendingTransaction(final PendingTransaction pendingTransaction) {
final long nonce = pendingTransaction.getNonce();
synchronized (pendingTransactions) {
if (!pendingTransactions.isEmpty()) {
final long expectedNext = pendingTransactions.lastKey() + 1;
if (nonce > (expectedNext) && nextGap.isEmpty()) {
nextGap = OptionalLong.of(expectedNext);
}
}
transactionsInfos.put(nonce, transactionInfo);
pendingTransactions.put(nonce, pendingTransaction);
if (nonce == nextGap.orElse(-1)) {
findGap();
}
}
}
public void removeTrackedTransactionInfo(final TransactionInfo txInfo) {
public void removeTrackedPendingTransaction(final PendingTransaction pendingTransaction) {
// check the value when removing, because it could have been replaced
if (transactionsInfos.remove(txInfo.getNonce(), txInfo)) {
synchronized (transactionsInfos) {
if (!transactionsInfos.isEmpty() && txInfo.getNonce() != transactionsInfos.firstKey()) {
if (pendingTransactions.remove(pendingTransaction.getNonce(), pendingTransaction)) {
synchronized (pendingTransactions) {
if (!pendingTransactions.isEmpty()
&& pendingTransaction.getNonce() != pendingTransactions.firstKey()) {
findGap();
}
}
@ -78,8 +78,8 @@ public class TransactionsForSenderInfo {
private void findGap() {
// find first gap
long expectedValue = transactionsInfos.firstKey();
for (final Long nonce : transactionsInfos.keySet()) {
long expectedValue = pendingTransactions.firstKey();
for (final Long nonce : pendingTransactions.keySet()) {
if (expectedValue == nonce) {
// no gap, keep moving
expectedValue++;
@ -92,35 +92,35 @@ public class TransactionsForSenderInfo {
}
public OptionalLong maybeNextNonce() {
if (transactionsInfos.isEmpty()) {
if (pendingTransactions.isEmpty()) {
return OptionalLong.empty();
} else {
return nextGap.isEmpty() ? OptionalLong.of(transactionsInfos.lastKey() + 1) : nextGap;
return nextGap.isEmpty() ? OptionalLong.of(pendingTransactions.lastKey() + 1) : nextGap;
}
}
public Optional<TransactionInfo> maybeLastTx() {
return Optional.ofNullable(transactionsInfos.lastEntry()).map(Map.Entry::getValue);
public Optional<PendingTransaction> maybeLastPendingTransaction() {
return Optional.ofNullable(pendingTransactions.lastEntry()).map(Map.Entry::getValue);
}
public int transactionCount() {
return transactionsInfos.size();
return pendingTransactions.size();
}
public Stream<TransactionInfo> streamTransactionInfos() {
return transactionsInfos.values().stream();
public Stream<PendingTransaction> streamPendingTransactions() {
return pendingTransactions.values().stream();
}
public TransactionInfo getTransactionInfoForNonce(final long nonce) {
return transactionsInfos.get(nonce);
public PendingTransaction getPendingTransactionForNonce(final long nonce) {
return pendingTransactions.get(nonce);
}
public String toTraceLog() {
return "{"
+ "senderAccount "
+ maybeSenderAccount
+ ", transactions "
+ transactionsInfos.entrySet().stream()
+ ", pendingTransactions "
+ pendingTransactions.entrySet().stream()
.map(e -> "(" + e.getKey() + ")" + e.getValue().toTraceLog())
.collect(Collectors.joining("; "))
+ ", nextGap "

@ -23,7 +23,7 @@ import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.TransactionTestFixture;
import org.hyperledger.besu.ethereum.eth.manager.EthPeer;
import org.hyperledger.besu.ethereum.eth.manager.ethtaskutils.PeerMessageTaskTest;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionAddedStatus;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedStatus;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import org.hyperledger.besu.plugin.services.MetricsSystem;

@ -15,9 +15,9 @@
package org.hyperledger.besu.ethereum.eth.transactions;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionAddedStatus.ADDED;
import static org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionAddedStatus.ALREADY_KNOWN;
import static org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionAddedStatus.REJECTED_UNDERPRICED_REPLACEMENT;
import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedStatus.ADDED;
import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedStatus.ALREADY_KNOWN;
import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedStatus.REJECTED_UNDERPRICED_REPLACEMENT;
import static org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionSelectionResult.COMPLETE_OPERATION;
import static org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionSelectionResult.CONTINUE;
import static org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionSelectionResult.DELETE_TRANSACTION_AND_CONTINUE;

@ -15,7 +15,7 @@
package org.hyperledger.besu.ethereum.eth.transactions;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionAddedStatus.ALREADY_KNOWN;
import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedStatus.ALREADY_KNOWN;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@ -15,7 +15,7 @@
package org.hyperledger.besu.ethereum.eth.transactions;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionInfo.toTransactionList;
import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction.toTransactionList;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
@ -33,7 +33,6 @@ import org.hyperledger.besu.ethereum.eth.manager.EthPeers;
import org.hyperledger.besu.ethereum.eth.manager.EthScheduler;
import org.hyperledger.besu.ethereum.eth.messages.EthPV65;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionInfo;
import java.time.Instant;
import java.util.ArrayList;
@ -243,20 +242,20 @@ public class TransactionBroadcasterTest {
transactionTracker, transactionsMessageSender, newPooledTransactionHashesMessageSender);
}
private Set<TransactionInfo> setupTransactionPool(
private Set<PendingTransaction> setupTransactionPool(
final int numLocalTransactions, final int numRemoteTransactions) {
Set<TransactionInfo> txInfo = createTransactionInfoList(numLocalTransactions, true);
txInfo.addAll(createTransactionInfoList(numRemoteTransactions, false));
Set<PendingTransaction> pendingTxs = createPendingTransactionList(numLocalTransactions, true);
pendingTxs.addAll(createPendingTransactionList(numRemoteTransactions, false));
when(pendingTransactions.getTransactionInfo()).thenReturn(txInfo);
when(pendingTransactions.getPendingTransactions()).thenReturn(pendingTxs);
return txInfo;
return pendingTxs;
}
private Set<TransactionInfo> createTransactionInfoList(final int num, final boolean local) {
private Set<PendingTransaction> createPendingTransactionList(final int num, final boolean local) {
return IntStream.range(0, num)
.mapToObj(unused -> generator.transaction())
.map(tx -> new TransactionInfo(tx, local, Instant.now()))
.map(tx -> new PendingTransaction(tx, local, Instant.now()))
.collect(Collectors.toSet());
}

@ -23,7 +23,6 @@ import static org.mockito.Mockito.when;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionInfo;
import org.hyperledger.besu.plugin.data.TransactionType;
import java.util.Collection;
@ -55,19 +54,19 @@ public class TransactionPoolReplacementHandlerTest {
}
private final List<TransactionPoolReplacementRule> rules;
private final TransactionInfo oldTransactionInfo;
private final TransactionInfo newTransactionInfo;
private final PendingTransaction oldPendingTransaction;
private final PendingTransaction newPendingTransaction;
private final boolean expectedResult;
private final BlockHeader header;
public TransactionPoolReplacementHandlerTest(
final List<TransactionPoolReplacementRule> rules,
final TransactionInfo oldTransactionInfo,
final TransactionInfo newTransactionInfo,
final PendingTransaction oldPendingTransaction,
final PendingTransaction newPendingTransaction,
final boolean expectedResult) {
this.rules = rules;
this.oldTransactionInfo = oldTransactionInfo;
this.newTransactionInfo = newTransactionInfo;
this.oldPendingTransaction = oldPendingTransaction;
this.newPendingTransaction = newPendingTransaction;
this.expectedResult = expectedResult;
header = mock(BlockHeader.class);
when(header.getBaseFee()).thenReturn(Optional.empty());
@ -77,7 +76,7 @@ public class TransactionPoolReplacementHandlerTest {
public void shouldReplace() {
assertThat(
new TransactionPoolReplacementHandler(rules)
.shouldReplace(oldTransactionInfo, newTransactionInfo, header))
.shouldReplace(oldPendingTransaction, newPendingTransaction, header))
.isEqualTo(expectedResult);
}
@ -92,11 +91,11 @@ public class TransactionPoolReplacementHandlerTest {
.collect(Collectors.toList());
}
private static TransactionInfo mockTransactionInfo() {
final TransactionInfo transactionInfo = mock(TransactionInfo.class);
private static PendingTransaction mockTransactionInfo() {
final PendingTransaction pendingTransaction = mock(PendingTransaction.class);
final Transaction transaction = mock(Transaction.class);
when(transaction.getType()).thenReturn(TransactionType.FRONTIER);
when(transactionInfo.getTransaction()).thenReturn(transaction);
return transactionInfo;
when(pendingTransaction.getTransaction()).thenReturn(transaction);
return pendingTransaction;
}
}

@ -23,7 +23,6 @@ import static org.mockito.Mockito.when;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter.TransactionInfo;
import org.hyperledger.besu.plugin.data.TransactionType;
import org.hyperledger.besu.util.number.Percentage;
@ -82,15 +81,15 @@ public class TransactionReplacementRulesTest {
});
}
private final TransactionInfo oldTx;
private final TransactionInfo newTx;
private final PendingTransaction oldTx;
private final PendingTransaction newTx;
private final Optional<Wei> baseFee;
private final int priceBump;
private final boolean expected;
public TransactionReplacementRulesTest(
final TransactionInfo oldTx,
final TransactionInfo newTx,
final PendingTransaction oldTx,
final PendingTransaction newTx,
final Optional<Wei> baseFee,
final int priceBump,
final boolean expected) {
@ -112,22 +111,22 @@ public class TransactionReplacementRulesTest {
.isEqualTo(expected);
}
private static TransactionInfo frontierTx(final long price) {
final TransactionInfo transactionInfo = mock(TransactionInfo.class);
private static PendingTransaction frontierTx(final long price) {
final PendingTransaction pendingTransaction = mock(PendingTransaction.class);
final Transaction transaction =
Transaction.builder()
.chainId(BigInteger.ZERO)
.type(TransactionType.FRONTIER)
.gasPrice(Wei.of(price))
.build();
when(transactionInfo.getTransaction()).thenReturn(transaction);
when(transactionInfo.getGasPrice()).thenReturn(Wei.of(price));
return transactionInfo;
when(pendingTransaction.getTransaction()).thenReturn(transaction);
when(pendingTransaction.getGasPrice()).thenReturn(Wei.of(price));
return pendingTransaction;
}
private static TransactionInfo eip1559Tx(
private static PendingTransaction eip1559Tx(
final long maxPriorityFeePerGas, final long maxFeePerGas) {
final TransactionInfo transactionInfo = mock(TransactionInfo.class);
final PendingTransaction pendingTransaction = mock(PendingTransaction.class);
final Transaction transaction =
Transaction.builder()
.chainId(BigInteger.ZERO)
@ -135,7 +134,7 @@ public class TransactionReplacementRulesTest {
.maxPriorityFeePerGas(Wei.of(maxPriorityFeePerGas))
.maxFeePerGas(Wei.of(maxFeePerGas))
.build();
when(transactionInfo.getTransaction()).thenReturn(transaction);
return transactionInfo;
when(pendingTransaction.getTransaction()).thenReturn(transaction);
return pendingTransaction;
}
}

Loading…
Cancel
Save