Skip to content

Commit

Permalink
[EIP-1559] Transaction pool pricing rules must handle both frontier a…
Browse files Browse the repository at this point in the history
…nd EIP-1559 transactions. (hyperledger#919)

* The transaction gas price is computed when adding a transaction into the local pool using eth_sendRawTransaction JSON RPC endpoint. Transaction price must be computed properly depending on the type of the transaction.
For instance `shouldReplace` method of PendingTransactions must be updated to deal with EIP-1559 transactions.

- Updated `PendingTransactions` to add access to the chain header in order to retrieve the last base fee value.
- Updated `TransactionReplacementByPriceRule` to compute the transaction price depending on the type of the transaction (frontier or eip-1559).
- Added unit tests to cover all possible replacement scenarios.

Signed-off-by: Abdelhamid Bakhta <abdelhamid.bakhta@consensys.net>
Signed-off-by: Sally MacFarlane <sally.macfarlane@consensys.net>
  • Loading branch information
AbdelStark authored and macfarla committed Jun 3, 2020
1 parent f29ae17 commit 9f0ceff
Show file tree
Hide file tree
Showing 18 changed files with 172 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ public void proposerAddressCanBeExtractFromAConstructedBlock() {
5,
5,
TestClock.fixed(),
metricsSystem),
metricsSystem,
blockchain::getChainHeadHeader),
protocolContext,
protocolSchedule,
gasLimit -> gasLimit,
Expand Down Expand Up @@ -159,7 +160,8 @@ public void insertsValidVoteIntoConstructedBlock() {
5,
5,
TestClock.fixed(),
metricsSystem),
metricsSystem,
blockchain::getChainHeadHeader),
protocolContext,
protocolSchedule,
gasLimit -> gasLimit,
Expand Down Expand Up @@ -191,7 +193,8 @@ public void insertsNoVoteWhenAuthInValidators() {
5,
5,
TestClock.fixed(),
metricsSystem),
metricsSystem,
blockchain::getChainHeadHeader),
protocolContext,
protocolSchedule,
gasLimit -> gasLimit,
Expand Down Expand Up @@ -226,7 +229,8 @@ public void insertsNoVoteWhenAtEpoch() {
5,
5,
TestClock.fixed(),
metricsSystem),
metricsSystem,
blockchain::getChainHeadHeader),
protocolContext,
protocolSchedule,
gasLimit -> gasLimit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ public void extraDataCreatedOnEpochBlocksContainsValidators() {
1,
5,
TestClock.fixed(),
metricsSystem),
metricsSystem,
() -> null),
proposerNodeKey,
new MiningParameters(AddressHelpers.ofValue(1), Wei.ZERO, vanityData, false),
mock(CliqueBlockScheduler.class),
Expand Down Expand Up @@ -138,7 +139,8 @@ public void extraDataForNonEpochBlocksDoesNotContainValidaors() {
1,
5,
TestClock.fixed(),
metricsSystem),
metricsSystem,
() -> null),
proposerNodeKey,
new MiningParameters(AddressHelpers.ofValue(1), Wei.ZERO, vanityData, false),
mock(CliqueBlockScheduler.class),
Expand Down Expand Up @@ -176,7 +178,8 @@ public void shouldUseLatestVanityData() {
1,
5,
TestClock.fixed(),
metricsSystem),
metricsSystem,
() -> null),
proposerNodeKey,
new MiningParameters(AddressHelpers.ofValue(1), Wei.ZERO, initialVanityData, false),
mock(CliqueBlockScheduler.class),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,12 @@ private static ControllerAndState createControllerAndFinalState(

final PendingTransactions pendingTransactions =
new PendingTransactions(
TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS, 1, 1, clock, metricsSystem);
TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS,
1,
1,
clock,
metricsSystem,
blockChain::getChainHeadHeader);

final IbftBlockCreatorFactory blockCreatorFactory =
new IbftBlockCreatorFactory(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ public void createdBlockPassesValidationRulesAndHasAppropriateHashAndMixHash() {
1,
5,
TestClock.fixed(),
metricsSystem);
metricsSystem,
blockchain::getChainHeadHeader);

final IbftBlockCreator blockCreator =
new IbftBlockCreator(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ public void headerProducedPassesValidationRules() {
1,
5,
TestClock.fixed(),
metricsSystem),
metricsSystem,
blockchain::getChainHeadHeader),
protContext,
protocolSchedule,
parentGasLimit -> parentGasLimit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,7 @@ public class EthGetFilterChangesIntegrationTest {
private TransactionPool transactionPool;
private final MetricsSystem metricsSystem = new NoOpMetricsSystem();

private final PendingTransactions transactions =
new PendingTransactions(
TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS,
MAX_TRANSACTIONS,
MAX_HASHES,
TestClock.fixed(),
metricsSystem);
private PendingTransactions transactions;

private static final int MAX_TRANSACTIONS = 5;
private static final int MAX_HASHES = 5;
Expand All @@ -100,6 +94,14 @@ public class EthGetFilterChangesIntegrationTest {
public void setUp() {
final ExecutionContextTestFixture executionContext = ExecutionContextTestFixture.create();
blockchain = executionContext.getBlockchain();
transactions =
new PendingTransactions(
TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS,
MAX_TRANSACTIONS,
MAX_HASHES,
TestClock.fixed(),
metricsSystem,
blockchain::getChainHeadHeader);
final ProtocolContext<Void> protocolContext = executionContext.getProtocolContext();

PeerTransactionTracker peerTransactionTracker = mock(PeerTransactionTracker.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,15 @@ public class BlockTransactionSelectorTest {
private static final KeyPair keyPair = KeyPair.generate();
private final MetricsSystem metricsSystem = new NoOpMetricsSystem();

private final Blockchain blockchain = new TestBlockchain();
private final PendingTransactions pendingTransactions =
new PendingTransactions(
TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS,
5,
5,
TestClock.fixed(),
metricsSystem);
private final Blockchain blockchain = new TestBlockchain();
metricsSystem,
blockchain::getChainHeadHeader);
private final MutableWorldState worldState = InMemoryStorageProvider.createInMemoryWorldState();
private final Supplier<Boolean> isCancelled = () -> false;
private final TransactionProcessor transactionProcessor = mock(TransactionProcessor.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ public void createMainnetBlock1() throws IOException {
1,
5,
TestClock.fixed(),
metricsSystem);
metricsSystem,
executionContextTestFixture.getProtocolContext().getBlockchain()::getChainHeadHeader);

final EthHashBlockCreator blockCreator =
new EthHashBlockCreator(
Expand Down Expand Up @@ -135,7 +136,8 @@ public void createMainnetBlock1_fixedDifficulty1() {
1,
5,
TestClock.fixed(),
metricsSystem);
metricsSystem,
executionContextTestFixture.getProtocolContext().getBlockchain()::getChainHeadHeader);

final EthHashBlockCreator blockCreator =
new EthHashBlockCreator(
Expand Down Expand Up @@ -181,7 +183,8 @@ public void rewardBeneficiary_zeroReward_skipZeroRewardsFalse() {
1,
5,
TestClock.fixed(),
metricsSystem);
metricsSystem,
executionContextTestFixture.getProtocolContext().getBlockchain()::getChainHeadHeader);

final EthHashBlockCreator blockCreator =
new EthHashBlockCreator(
Expand Down Expand Up @@ -243,7 +246,8 @@ public void rewardBeneficiary_zeroReward_skipZeroRewardsTrue() {
1,
5,
TestClock.fixed(),
metricsSystem);
metricsSystem,
executionContextTestFixture.getProtocolContext().getBlockchain()::getChainHeadHeader);

final EthHashBlockCreator blockCreator =
new EthHashBlockCreator(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ public void startingMiningWithoutCoinbaseThrowsException() {
1,
5,
TestClock.fixed(),
metricsSystem);
metricsSystem,
() -> null);

final EthHashMinerExecutor executor =
new EthHashMinerExecutor(
Expand All @@ -69,7 +70,8 @@ public void settingCoinbaseToNullThrowsException() {
1,
5,
TestClock.fixed(),
metricsSystem);
metricsSystem,
() -> null);

final EthHashMinerExecutor executor =
new EthHashMinerExecutor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions.TransactionAddedStatus.ALREADY_KNOWN;
import static org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions.TransactionAddedStatus.REJECTED_UNDERPRICED_REPLACEMENT;

import org.hyperledger.besu.config.experimental.ExperimentalEIPs;
import org.hyperledger.besu.ethereum.core.AccountTransactionOrder;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInvalidReason;
Expand Down Expand Up @@ -48,6 +50,7 @@
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import com.google.common.annotations.VisibleForTesting;
Expand Down Expand Up @@ -87,17 +90,20 @@ public class PendingTransactions {
private final long maxPendingTransactions;
private final TransactionPoolReplacementHandler transactionReplacementHandler =
new TransactionPoolReplacementHandler();
private final Supplier<BlockHeader> chainHeadHeaderSupplier;

public PendingTransactions(
final int maxTransactionRetentionHours,
final int maxPendingTransactions,
final int maxPooledTransactionHashes,
final Clock clock,
final MetricsSystem metricsSystem) {
final MetricsSystem metricsSystem,
final Supplier<BlockHeader> chainHeadHeaderSupplier) {
this.maxTransactionRetentionHours = maxTransactionRetentionHours;
this.maxPendingTransactions = maxPendingTransactions;
this.clock = clock;
this.newPooledHashes = EvictingQueue.create(maxPooledTransactionHashes);
this.chainHeadHeaderSupplier = chainHeadHeaderSupplier;
final LabelledMetric<Counter> transactionAddedCounter =
metricsSystem.createLabelledCounter(
BesuMetricCategory.TRANSACTION_POOL,
Expand Down Expand Up @@ -269,7 +275,14 @@ private TransactionAddedStatus addTransactionForSenderAndNonce(
final TransactionInfo existingTransaction =
getTrackedTransactionBySenderAndNonce(transactionInfo);
if (existingTransaction != null) {
if (!transactionReplacementHandler.shouldReplace(existingTransaction, transactionInfo)) {
final Optional<Long> baseFee =
ExperimentalEIPs.eip1559Enabled
&& (existingTransaction.getTransaction().isEIP1559Transaction()
|| transactionInfo.getTransaction().isEIP1559Transaction())
? chainHeadHeaderSupplier.get().getBaseFee()
: Optional.empty();
if (!transactionReplacementHandler.shouldReplace(
existingTransaction, transactionInfo, baseFee)) {
return REJECTED_UNDERPRICED_REPLACEMENT;
}
removeTransaction(existingTransaction.getTransaction());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ public static TransactionPool createTransactionPool(
transactionPoolConfiguration.getTxPoolMaxSize(),
transactionPoolConfiguration.getPooledTransactionHashesSize(),
clock,
metricsSystem);
metricsSystem,
protocolContext.getBlockchain()::getChainHeadHeader);

final PeerTransactionTracker transactionTracker = new PeerTransactionTracker();
final TransactionsMessageSender transactionsMessageSender =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions.TransactionInfo;

import java.util.List;
import java.util.Optional;

import com.google.common.annotations.VisibleForTesting;

Expand All @@ -36,12 +37,14 @@ public TransactionPoolReplacementHandler() {

@Override
public boolean shouldReplace(
final TransactionInfo existingTransactionInfo, final TransactionInfo newTransactionInfo) {
final TransactionInfo existingTransactionInfo,
final TransactionInfo newTransactionInfo,
final Optional<Long> baseFee) {
assert existingTransactionInfo != null;
if (newTransactionInfo == null) {
return false;
}
return rules.stream()
.anyMatch(rule -> rule.shouldReplace(existingTransactionInfo, newTransactionInfo));
.anyMatch(rule -> rule.shouldReplace(existingTransactionInfo, newTransactionInfo, baseFee));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@

import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions.TransactionInfo;

import java.util.Optional;

@FunctionalInterface
public interface TransactionPoolReplacementRule {

boolean shouldReplace(
TransactionInfo existingTransactionInfo, TransactionInfo newTransactionInfo);
TransactionInfo existingTransactionInfo,
TransactionInfo newTransactionInfo,
Optional<Long> baseFee);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,34 @@
*/
package org.hyperledger.besu.ethereum.eth.transactions;

import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.core.fees.TransactionPriceCalculator;
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions.TransactionInfo;

import java.util.Optional;

public class TransactionReplacementByPriceRule implements TransactionPoolReplacementRule {
private static final TransactionPriceCalculator FRONTIER_CALCULATOR =
TransactionPriceCalculator.frontier();
private static final TransactionPriceCalculator EIP1559_CALCULATOR =
TransactionPriceCalculator.eip1559();

@Override
public boolean shouldReplace(
final TransactionInfo existingTransactionInfo, final TransactionInfo newTransactionInfo) {
final TransactionInfo existingTransactionInfo,
final TransactionInfo newTransactionInfo,
final Optional<Long> baseFee) {
assert existingTransactionInfo.getTransaction() != null
&& newTransactionInfo.getTransaction() != null;
return newTransactionInfo
.getTransaction()
.getGasPrice()
.compareTo(existingTransactionInfo.getTransaction().getGasPrice())
return priceOf(newTransactionInfo.getTransaction(), baseFee)
.compareTo(priceOf(existingTransactionInfo.getTransaction(), baseFee))
> 0;
}

private Wei priceOf(final Transaction transaction, final Optional<Long> baseFee) {
final TransactionPriceCalculator transactionPriceCalculator =
transaction.isEIP1559Transaction() ? EIP1559_CALCULATOR : FRONTIER_CALCULATOR;
return transactionPriceCalculator.price(transaction, baseFee);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ public class PendingTransactionsTest {
MAX_TRANSACTIONS,
MAX_TRANSACTION_HASHES,
TestClock.fixed(),
metricsSystem);
metricsSystem,
() -> null);
private final Transaction transaction1 = createTransaction(2);
private final Transaction transaction2 = createTransaction(1);

Expand Down Expand Up @@ -559,7 +560,8 @@ public void shouldEvictMultipleOldTransactions() {
MAX_TRANSACTIONS,
MAX_TRANSACTION_HASHES,
clock,
metricsSystem);
metricsSystem,
() -> null);

transactions.addRemoteTransaction(transaction1);
assertThat(transactions.size()).isEqualTo(1);
Expand All @@ -581,7 +583,8 @@ public void shouldEvictSingleOldTransaction() {
MAX_TRANSACTIONS,
MAX_TRANSACTION_HASHES,
clock,
metricsSystem);
metricsSystem,
() -> null);
transactions.addRemoteTransaction(transaction1);
assertThat(transactions.size()).isEqualTo(1);
clock.step(2L, ChronoUnit.HOURS);
Expand All @@ -599,7 +602,8 @@ public void shouldEvictExclusivelyOldTransactions() {
MAX_TRANSACTIONS,
MAX_TRANSACTION_HASHES,
clock,
metricsSystem);
metricsSystem,
() -> null);
transactions.addRemoteTransaction(transaction1);
assertThat(transactions.size()).isEqualTo(1);
clock.step(3L, ChronoUnit.HOURS);
Expand Down
Loading

0 comments on commit 9f0ceff

Please sign in to comment.