Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support free gas networks when using London fee market #4003

Merged
merged 8 commits into from
Jun 30, 2022
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
### Bug Fixes
- Fixed a snapsync issue that can sometimes block the healing step [#3920](https://github.com/hyperledger/besu/pull/3920)
- Upgrade OpenTelemetry to version 1.15.0 [#3675](https://github.com/hyperledger/besu/pull/3675)
- Support free gas networks in the London fee market [#4003](https://github.com/hyperledger/besu/pull/4003)

## 22.4.3

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,14 @@ public class LondonFeeMarket implements BaseFeeMarket {
GenesisConfigFile.BASEFEE_AT_GENESIS_DEFAULT_VALUE;
static final long DEFAULT_BASEFEE_MAX_CHANGE_DENOMINATOR = 8L;
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 final Wei baseFeeInitialValue;
private final long londonForkBlockNumber;
private final TransactionPriceCalculator txPriceCalculator;
private Wei baseFeeFloor = DEFAULT_BASEFEE_FLOOR;

public LondonFeeMarket(final long londonForkBlockNumber) {
this(londonForkBlockNumber, Optional.empty());
Expand All @@ -47,6 +50,14 @@ public LondonFeeMarket(
this.txPriceCalculator = TransactionPriceCalculator.eip1559();
this.londonForkBlockNumber = londonForkBlockNumber;
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
Expand Down Expand Up @@ -81,12 +92,11 @@ public Wei minTransactionPriceInNextBlock(

@Override
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 7 wei
// ensure effective baseFee is at least above floor
return txn.getGasPrice()
.map(Optional::of)
.orElse(txn.getMaxFeePerGas())
.filter(fee -> fee.greaterOrEqualThan(Wei.of(7L)))
.filter(fee -> fee.greaterOrEqualThan(baseFeeFloor))
.isPresent();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ public static Collection<Object[]> data() {
new Object[][] {
// legacy transaction must return gas price
{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
{
FRONTIER_CALCULATOR,
Expand All @@ -92,6 +94,8 @@ public static Collection<Object[]> data() {
Optional.of(Wei.of(150L)),
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
{
EIP_1559_CALCULATOR,
Expand Down Expand Up @@ -121,7 +125,9 @@ public static Collection<Object[]> data() {
Wei.of(300L),
Optional.of(Wei.of(250L)),
Wei.of(300L)
}
},
// EIP-1559 transaction zero price
{EIP_1559_CALCULATOR, EIP1559, null, Wei.ZERO, Wei.ZERO, Optional.of(Wei.ZERO), Wei.ZERO}
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,29 @@ public void shouldRejectTransactionIfEIP1559TransactionGasPriceLessBaseFee() {
.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
public void shouldAcceptValidEIP1559() {
final MainnetTransactionValidator validator =
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockBody;
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.Difficulty;
import org.hyperledger.besu.ethereum.core.ExecutionContextTestFixture;
Expand All @@ -61,6 +62,7 @@
import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer;
import org.hyperledger.besu.ethereum.eth.messages.EthPV65;
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.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
Expand Down Expand Up @@ -999,6 +1001,78 @@ public void shouldRejectZeroGasPriceFrontierTransactionsWhenNotMining() {
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) {
assertThat(transactions.getTransactionByHash(t.getHash())).contains(t);
}
Expand Down