Support free gas networks when using London fee market (#4003)

If baseFeePerGas is configured to "0x0" in the genesis file, bypass the need for a transaction to have a gas price of >= 7 Wei

Ensure baseFeeFloor is either 0 or >= 7 to avoid integer arithmetic issues.

Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
pull/4032/head
Simon Dudley 2 years ago committed by GitHub
parent 17de636fe2
commit 6031ed43e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 16
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LondonFeeMarket.java
  3. 8
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculatorTest.java
  4. 23
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidatorTest.java
  5. 81
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LondonFeeMarketTest.java
  6. 74
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolTest.java

@ -9,6 +9,7 @@
### Bug Fixes ### Bug Fixes
- Fixed a snapsync issue that can sometimes block the healing step [#3920](https://github.com/hyperledger/besu/pull/3920) - Fixed a snapsync issue that can sometimes block the healing step [#3920](https://github.com/hyperledger/besu/pull/3920)
- Support free gas networks in the London fee market [#4003](https://github.com/hyperledger/besu/pull/4003)
## 22.4.3 ## 22.4.3

@ -32,11 +32,14 @@ public class LondonFeeMarket implements BaseFeeMarket {
GenesisConfigFile.BASEFEE_AT_GENESIS_DEFAULT_VALUE; GenesisConfigFile.BASEFEE_AT_GENESIS_DEFAULT_VALUE;
static final long DEFAULT_BASEFEE_MAX_CHANGE_DENOMINATOR = 8L; static final long DEFAULT_BASEFEE_MAX_CHANGE_DENOMINATOR = 8L;
static final long DEFAULT_SLACK_COEFFICIENT = 2L; static final long DEFAULT_SLACK_COEFFICIENT = 2L;
// required for integer arithmetic to work when baseFee > 0
private static final Wei DEFAULT_BASEFEE_FLOOR = Wei.of(7L);
private static final Logger LOG = LoggerFactory.getLogger(LondonFeeMarket.class); private static final Logger LOG = LoggerFactory.getLogger(LondonFeeMarket.class);
private final Wei baseFeeInitialValue; private final Wei baseFeeInitialValue;
private final long londonForkBlockNumber; private final long londonForkBlockNumber;
private final TransactionPriceCalculator txPriceCalculator; private final TransactionPriceCalculator txPriceCalculator;
private Wei baseFeeFloor = DEFAULT_BASEFEE_FLOOR;
public LondonFeeMarket(final long londonForkBlockNumber) { public LondonFeeMarket(final long londonForkBlockNumber) {
this(londonForkBlockNumber, Optional.empty()); this(londonForkBlockNumber, Optional.empty());
@ -47,6 +50,14 @@ public class LondonFeeMarket implements BaseFeeMarket {
this.txPriceCalculator = TransactionPriceCalculator.eip1559(); this.txPriceCalculator = TransactionPriceCalculator.eip1559();
this.londonForkBlockNumber = londonForkBlockNumber; this.londonForkBlockNumber = londonForkBlockNumber;
this.baseFeeInitialValue = baseFeePerGasOverride.orElse(DEFAULT_BASEFEE_INITIAL_VALUE); this.baseFeeInitialValue = baseFeePerGasOverride.orElse(DEFAULT_BASEFEE_INITIAL_VALUE);
if (baseFeeInitialValue.isZero()) {
baseFeeFloor = Wei.ZERO;
} else if (baseFeeInitialValue.lessThan(DEFAULT_BASEFEE_FLOOR)) {
throw new IllegalStateException(
String.format(
"baseFee must be either 0 or > %s wei to avoid integer arithmetic issues",
DEFAULT_BASEFEE_FLOOR));
}
} }
@Override @Override
@ -81,12 +92,11 @@ public class LondonFeeMarket implements BaseFeeMarket {
@Override @Override
public boolean satisfiesFloorTxCost(final Transaction txn) { public boolean satisfiesFloorTxCost(final Transaction txn) {
// London fee market arithmetic never allows for a base fee below 7 wei // ensure effective baseFee is at least above floor
// ensure effective baseFee is at least 7 wei
return txn.getGasPrice() return txn.getGasPrice()
.map(Optional::of) .map(Optional::of)
.orElse(txn.getMaxFeePerGas()) .orElse(txn.getMaxFeePerGas())
.filter(fee -> fee.greaterOrEqualThan(Wei.of(7L))) .filter(fee -> fee.greaterOrEqualThan(baseFeeFloor))
.isPresent(); .isPresent();
} }

@ -72,6 +72,8 @@ public class TransactionPriceCalculatorTest {
new Object[][] { new Object[][] {
// legacy transaction must return gas price // legacy transaction must return gas price
{FRONTIER_CALCULATOR, FRONTIER, Wei.of(578L), null, null, Optional.empty(), Wei.of(578L)}, {FRONTIER_CALCULATOR, FRONTIER, Wei.of(578L), null, null, Optional.empty(), Wei.of(578L)},
// legacy transaction zero price
{FRONTIER_CALCULATOR, FRONTIER, Wei.ZERO, null, null, Optional.empty(), Wei.ZERO},
// ACCESSLIST transaction must return gas price // ACCESSLIST transaction must return gas price
{ {
FRONTIER_CALCULATOR, FRONTIER_CALCULATOR,
@ -92,6 +94,8 @@ public class TransactionPriceCalculatorTest {
Optional.of(Wei.of(150L)), Optional.of(Wei.of(150L)),
Wei.of(578L) Wei.of(578L)
}, },
// london legacy transaction zero price
{EIP_1559_CALCULATOR, FRONTIER, Wei.ZERO, null, null, Optional.of(Wei.ZERO), Wei.ZERO},
// ACCESSLIST transaction must return gas price // ACCESSLIST transaction must return gas price
{ {
EIP_1559_CALCULATOR, EIP_1559_CALCULATOR,
@ -121,7 +125,9 @@ public class TransactionPriceCalculatorTest {
Wei.of(300L), Wei.of(300L),
Optional.of(Wei.of(250L)), Optional.of(Wei.of(250L)),
Wei.of(300L) Wei.of(300L)
} },
// EIP-1559 transaction zero price
{EIP_1559_CALCULATOR, EIP1559, null, Wei.ZERO, Wei.ZERO, Optional.of(Wei.ZERO), Wei.ZERO}
}); });
} }

@ -395,6 +395,29 @@ public class MainnetTransactionValidatorTest {
.isEqualTo(ValidationResult.invalid(INVALID_TRANSACTION_FORMAT)); .isEqualTo(ValidationResult.invalid(INVALID_TRANSACTION_FORMAT));
} }
@Test
public void shouldAcceptZeroGasPriceTransactionIfBaseFeeIsZero() {
final Optional<Wei> zeroBaseFee = Optional.of(Wei.ZERO);
final MainnetTransactionValidator validator =
new MainnetTransactionValidator(
gasCalculator,
FeeMarket.london(0L, zeroBaseFee),
false,
Optional.of(BigInteger.ONE),
Set.of(TransactionType.FRONTIER, TransactionType.EIP1559),
defaultGoQuorumCompatibilityMode);
final Transaction transaction =
new TransactionTestFixture()
.type(TransactionType.EIP1559)
.maxPriorityFeePerGas(Optional.of(Wei.ZERO))
.maxFeePerGas(Optional.of(Wei.ZERO))
.chainId(Optional.of(BigInteger.ONE))
.createTransaction(senderKeys);
assertThat(validator.validate(transaction, zeroBaseFee, transactionValidationParams))
.isEqualTo(ValidationResult.valid());
}
@Test @Test
public void shouldAcceptValidEIP1559() { public void shouldAcceptValidEIP1559() {
final MainnetTransactionValidator validator = final MainnetTransactionValidator validator =

@ -0,0 +1,81 @@
/*
* Copyright Hyperledger 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.mainnet.feemarket;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import org.hyperledger.besu.crypto.KeyPair;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.TransactionTestFixture;
import org.hyperledger.besu.plugin.data.TransactionType;
import java.util.Optional;
import org.junit.Test;
public class LondonFeeMarketTest {
private static final KeyPair KEY_PAIR1 =
SignatureAlgorithmFactory.getInstance().generateKeyPair();
@Test
public void satisfiesFloorTxCost() {
final Transaction transaction =
new TransactionTestFixture()
.type(TransactionType.FRONTIER)
.gasPrice(Wei.of(7))
.createTransaction(KEY_PAIR1);
final LondonFeeMarket londonFeeMarket = new LondonFeeMarket(0);
assertThat(londonFeeMarket.satisfiesFloorTxCost(transaction)).isTrue();
}
@Test
public void maxFeePerGasLessThanMinimumBaseFee() {
final Transaction transaction =
new TransactionTestFixture()
.type(TransactionType.EIP1559)
.maxFeePerGas(Optional.of(Wei.of(6)))
.maxPriorityFeePerGas(Optional.of(Wei.of(0)))
.gasPrice(null)
.createTransaction(KEY_PAIR1);
final LondonFeeMarket londonFeeMarket = new LondonFeeMarket(0);
assertThat(londonFeeMarket.satisfiesFloorTxCost(transaction)).isFalse();
}
@Test
public void satisfiesFloorTxCostWhenBaseFeeInitialValueIsZero() {
final Transaction transaction =
new TransactionTestFixture()
.type(TransactionType.EIP1559)
.maxFeePerGas(Optional.of(Wei.ZERO))
.maxPriorityFeePerGas(Optional.of(Wei.ZERO))
.gasPrice(null)
.createTransaction(KEY_PAIR1);
final LondonFeeMarket londonFeeMarket = new LondonFeeMarket(0, Optional.of(Wei.ZERO));
assertThat(londonFeeMarket.satisfiesFloorTxCost(transaction)).isTrue();
}
@Test
public void throwsWhenBaseFeeOverrideIsNonZeroAndBelowFloor() {
assertThatThrownBy(() -> new LondonFeeMarket(0, Optional.of(Wei.of(6))))
.isInstanceOf(IllegalStateException.class);
}
}

@ -45,6 +45,7 @@ import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockBody;
import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder;
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.Difficulty;
import org.hyperledger.besu.ethereum.core.ExecutionContextTestFixture; import org.hyperledger.besu.ethereum.core.ExecutionContextTestFixture;
@ -61,6 +62,7 @@ import org.hyperledger.besu.ethereum.eth.manager.EthScheduler;
import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer; import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer;
import org.hyperledger.besu.ethereum.eth.messages.EthPV65; import org.hyperledger.besu.ethereum.eth.messages.EthPV65;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.GasPricePendingTransactionsSorter; import org.hyperledger.besu.ethereum.eth.transactions.sorter.GasPricePendingTransactionsSorter;
import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions;
import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionValidator; import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionValidator;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
@ -999,6 +1001,78 @@ public class TransactionPoolTest {
assertThat(result.getInvalidReason()).isEqualTo(TransactionInvalidReason.GAS_PRICE_TOO_LOW); assertThat(result.getInvalidReason()).isEqualTo(TransactionInvalidReason.GAS_PRICE_TOO_LOW);
} }
@Test
public void shouldAcceptZeroGasPriceFrontierTxsWhenMinGasPriceIsZero() {
when(miningParameters.getMinTransactionGasPrice()).thenReturn(Wei.ZERO);
final Transaction transaction =
new TransactionTestFixture()
.type(TransactionType.FRONTIER)
.gasPrice(Wei.ZERO)
.value(Wei.ONE)
.chainId(Optional.of(BigInteger.ONE))
.createTransaction(KEY_PAIR1);
givenTransactionIsValid(transaction);
final ValidationResult<TransactionInvalidReason> result =
transactionPool.addLocalTransaction(transaction);
assertThat(result).isEqualTo(ValidationResult.valid());
}
@Test
public void shouldAcceptZeroGasPriceFrontierTxsWhenMinGasPriceIsZeroAndLondonWithZeroBaseFee() {
when(miningParameters.getMinTransactionGasPrice()).thenReturn(Wei.ZERO);
when(protocolSpec.getFeeMarket()).thenReturn(FeeMarket.london(0, Optional.of(Wei.ZERO)));
whenBlockBaseFeeIsZero();
final Transaction transaction =
new TransactionTestFixture()
.type(TransactionType.FRONTIER)
.gasPrice(Wei.ZERO)
.createTransaction(KEY_PAIR1);
givenTransactionIsValid(transaction);
final ValidationResult<TransactionInvalidReason> result =
transactionPool.addLocalTransaction(transaction);
assertThat(result).isEqualTo(ValidationResult.valid());
}
private void whenBlockBaseFeeIsZero() {
final BlockHeader header =
BlockHeaderBuilder.fromHeader(blockchain.getChainHeadHeader())
.baseFee(Wei.ZERO)
.blockHeaderFunctions(new MainnetBlockHeaderFunctions())
.parentHash(blockchain.getChainHeadHash())
.buildBlockHeader();
blockchain.appendBlock(new Block(header, BlockBody.empty()), emptyList());
}
@Test
public void shouldAcceptZeroGasPrice1559TxsWhenMinGasPriceIsZeroAndLondonWithZeroBaseFee() {
when(miningParameters.getMinTransactionGasPrice()).thenReturn(Wei.ZERO);
when(protocolSpec.getFeeMarket()).thenReturn(FeeMarket.london(0, Optional.of(Wei.ZERO)));
whenBlockBaseFeeIsZero();
final Transaction transaction =
new TransactionTestFixture()
.type(TransactionType.EIP1559)
.gasPrice(null)
.maxFeePerGas(Optional.of(Wei.ZERO))
.maxPriorityFeePerGas(Optional.of(Wei.ZERO))
.createTransaction(KEY_PAIR1);
givenTransactionIsValid(transaction);
final ValidationResult<TransactionInvalidReason> result =
transactionPool.addLocalTransaction(transaction);
assertThat(result).isEqualTo(ValidationResult.valid());
}
private void assertTransactionPending(final Transaction t) { private void assertTransactionPending(final Transaction t) {
assertThat(transactions.getTransactionByHash(t.getHash())).contains(t); assertThat(transactions.getTransactionByHash(t.getHash())).contains(t);
} }

Loading…
Cancel
Save