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

[PAN-3023] Add command line option for target gas limit #24

Merged
merged 8 commits into from
Sep 30, 2019
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.hyperledger.besu.cli.config.EthNetworkConfig;
import org.hyperledger.besu.controller.BesuController;
import org.hyperledger.besu.controller.BesuControllerBuilder;
import org.hyperledger.besu.controller.GasLimitCalculator;
import org.hyperledger.besu.controller.KeyPairUtil;
import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration;
import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration;
Expand Down Expand Up @@ -146,6 +147,7 @@ public void startNode(final BesuNode node) {
.clock(Clock.systemUTC())
.isRevertReasonEnabled(node.isRevertReasonEnabled())
.storageProvider(storageProvider)
.targetGasLimit(GasLimitCalculator.DEFAULT)
.build();
} catch (final IOException e) {
throw new RuntimeException("Error building BesuController", e);
Expand Down
9 changes: 8 additions & 1 deletion besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,12 @@ void setBannedNodeIds(final List<String> values) {
"The name of a file containing the private key used to sign privacy marker transactions. If unset, each will be signed with a random key.")
private final Path privacyMarkerTransactionSigningKeyPath = null;

@Option(
names = {"--target-gas-limit"},
description =
"Sets target gas limit per block. If set each blocks gas limit will approach this setting over time if the current gas limit is different.")
private final Long targetGasLimit = null;

@Option(
names = {"--tx-pool-max-size"},
paramLabel = MANDATORY_INTEGER_FORMAT_HELP,
Expand Down Expand Up @@ -1047,7 +1053,8 @@ public BesuControllerBuilder<?> getControllerBuilder() {
.storageProvider(keyStorageProvider(keyValueStorageName))
.isPruningEnabled(isPruningEnabled)
.pruningConfiguration(buildPruningConfiguration())
.genesisConfigOverrides(genesisConfigOverrides);
.genesisConfigOverrides(genesisConfigOverrides)
.targetGasLimit(targetGasLimit);
} catch (final IOException e) {
throw new ExecutionException(this.commandLine, "Invalid path", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public abstract class BesuControllerBuilder<C> {
protected Clock clock;
protected KeyPair nodeKeys;
protected boolean isRevertReasonEnabled;
protected GasLimitCalculator gasLimitCalculator;
private StorageProvider storageProvider;
private final List<Runnable> shutdownActions = new ArrayList<>();
private boolean isPruningEnabled;
Expand Down Expand Up @@ -180,6 +181,11 @@ public BesuControllerBuilder<C> genesisConfigOverrides(
return this;
}

public BesuControllerBuilder<C> targetGasLimit(final Long targetGasLimit) {
this.gasLimitCalculator = new GasLimitCalculator(targetGasLimit);
return this;
}

public BesuController<C> build() {
checkNotNull(genesisConfig, "Missing genesis config");
checkNotNull(syncConfig, "Missing sync config");
Expand All @@ -193,6 +199,7 @@ public BesuController<C> build() {
checkNotNull(transactionPoolConfiguration, "Missing transaction pool configuration");
checkNotNull(nodeKeys, "Missing node keys");
checkNotNull(storageProvider, "Must supply a storage provider");
checkNotNull(gasLimitCalculator, "Missing gas limit calculator");

prepForBuild();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ protected MiningCoordinator createMiningCoordinator(
protocolContext.getConsensusState().getVoteTallyCache(),
localAddress,
secondsBetweenBlocks),
epochManager);
epochManager,
gasLimitCalculator);
final CliqueMiningCoordinator miningCoordinator =
new CliqueMiningCoordinator(
protocolContext.getBlockchain(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2019 ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.hyperledger.besu.controller;

import java.util.function.Function;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class GasLimitCalculator implements Function<Long, Long> {
private static final Logger LOG = LogManager.getLogger();
public static final long ADJUSTMENT_FACTOR = 1024L;
public static final Long DEFAULT = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be OptionalLong, so we don't have to deal with empty.

Copy link
Contributor

@shemnon shemnon Sep 25, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per our coding conventions we should be using modern Java featurs such as optional instead of giving null contextual meaning.

edit Disregard, this was off of old code github was showing me.

private final long targetGasLimit;

public GasLimitCalculator(final Long targetGasLimit) {
shemnon marked this conversation as resolved.
Show resolved Hide resolved
this.targetGasLimit = targetGasLimit == null ? 0L : targetGasLimit;
}

@Override
public Long apply(final Long gasLimit) {
long newGasLimit;

if (targetGasLimit > gasLimit) {
cfelde marked this conversation as resolved.
Show resolved Hide resolved
newGasLimit = Math.min(targetGasLimit, gasLimit + ADJUSTMENT_FACTOR);
} else if (targetGasLimit < gasLimit) {
newGasLimit = Math.max(targetGasLimit, gasLimit - ADJUSTMENT_FACTOR);
} else {
return gasLimit;
Copy link
Contributor

@shemnon shemnon Sep 25, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returns should occur at the same level of logic, unless this is a guard block. If targetGasLimit were an optional this return could be come a guard block at the top of the method.

edit disregard, Github was showing me old code on my second review.

}

LOG.debug("Adjusting block gas limit from {} to {}", gasLimit, newGasLimit);

return newGasLimit;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ protected MiningCoordinator createMiningCoordinator(

final IbftBlockCreatorFactory blockCreatorFactory =
new IbftBlockCreatorFactory(
(gasLimit) -> gasLimit,
gasLimitCalculator,
transactionPool.getPendingTransactions(),
protocolContext,
protocolSchedule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ protected MiningCoordinator createMiningCoordinator(
new DefaultBlockScheduler(
MainnetBlockHeaderValidator.MINIMUM_SECONDS_SINCE_PARENT,
MainnetBlockHeaderValidator.TIMESTAMP_TOLERANCE_S,
clock));
clock),
gasLimitCalculator);

final EthHashMiningCoordinator miningCoordinator =
new EthHashMiningCoordinator(protocolContext.getBlockchain(), executor, syncState);
Expand Down
2 changes: 2 additions & 0 deletions besu/src/test/java/org/hyperledger/besu/PrivacyTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import org.hyperledger.besu.config.GenesisConfigFile;
import org.hyperledger.besu.controller.BesuController;
import org.hyperledger.besu.controller.GasLimitCalculator;
import org.hyperledger.besu.crypto.SECP256K1.KeyPair;
import org.hyperledger.besu.ethereum.core.Account;
import org.hyperledger.besu.ethereum.core.Address;
Expand Down Expand Up @@ -77,6 +78,7 @@ public void privacyPrecompiled() throws IOException {
.clock(TestClock.fixed())
.privacyParameters(privacyParameters)
.transactionPoolConfiguration(TransactionPoolConfiguration.builder().build())
.targetGasLimit(GasLimitCalculator.DEFAULT)
.build();

final Address privacyContractAddress = Address.privacyPrecompiled(ADDRESS);
Expand Down
4 changes: 4 additions & 0 deletions besu/src/test/java/org/hyperledger/besu/RunnerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.hyperledger.besu.cli.config.EthNetworkConfig;
import org.hyperledger.besu.config.GenesisConfigFile;
import org.hyperledger.besu.controller.BesuController;
import org.hyperledger.besu.controller.GasLimitCalculator;
import org.hyperledger.besu.controller.KeyPairUtil;
import org.hyperledger.besu.controller.MainnetBesuControllerBuilder;
import org.hyperledger.besu.crypto.SECP256K1.KeyPair;
Expand Down Expand Up @@ -143,6 +144,7 @@ private void syncFromGenesis(final SyncMode mode) throws Exception {
.clock(TestClock.fixed())
.transactionPoolConfiguration(TransactionPoolConfiguration.builder().build())
.storageProvider(createKeyValueStorageProvider(dbAhead))
.targetGasLimit(GasLimitCalculator.DEFAULT)
.build()) {
setupState(blockCount, controller.getProtocolSchedule(), controller.getProtocolContext());
}
Expand All @@ -162,6 +164,7 @@ private void syncFromGenesis(final SyncMode mode) throws Exception {
.clock(TestClock.fixed())
.transactionPoolConfiguration(TransactionPoolConfiguration.builder().build())
.storageProvider(createKeyValueStorageProvider(dbAhead))
.targetGasLimit(GasLimitCalculator.DEFAULT)
.build();
final String listenHost = InetAddress.getLoopbackAddress().getHostAddress();
final JsonRpcConfiguration aheadJsonRpcConfiguration = jsonRpcConfiguration();
Expand Down Expand Up @@ -220,6 +223,7 @@ private void syncFromGenesis(final SyncMode mode) throws Exception {
.privacyParameters(PrivacyParameters.DEFAULT)
.clock(TestClock.fixed())
.transactionPoolConfiguration(TransactionPoolConfiguration.builder().build())
.targetGasLimit(GasLimitCalculator.DEFAULT)
.build();
final EnodeURL enode = runnerAhead.getLocalEnode().get();
final EthNetworkConfig behindEthNetworkConfiguration =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.hyperledger.besu.chainimport.RlpBlockImporter;
import org.hyperledger.besu.config.GenesisConfigFile;
import org.hyperledger.besu.controller.BesuController;
import org.hyperledger.besu.controller.GasLimitCalculator;
import org.hyperledger.besu.crypto.SECP256K1.KeyPair;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.Block;
Expand Down Expand Up @@ -88,6 +89,7 @@ private static BesuController<?> createController() throws IOException {
.dataDirectory(dataDir)
.clock(TestClock.fixed())
.transactionPoolConfiguration(TransactionPoolConfiguration.builder().build())
.targetGasLimit(GasLimitCalculator.DEFAULT)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.hyperledger.besu.config.GenesisConfigFile;
import org.hyperledger.besu.config.JsonUtil;
import org.hyperledger.besu.controller.BesuController;
import org.hyperledger.besu.controller.GasLimitCalculator;
import org.hyperledger.besu.crypto.SECP256K1.KeyPair;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.Address;
Expand Down Expand Up @@ -425,6 +426,7 @@ protected BesuController<?> createController(final GenesisConfigFile genesisConf
.dataDirectory(dataDir)
.clock(TestClock.fixed())
.transactionPoolConfiguration(TransactionPoolConfiguration.builder().build())
.targetGasLimit(GasLimitCalculator.DEFAULT)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import org.hyperledger.besu.config.GenesisConfigFile;
import org.hyperledger.besu.controller.BesuController;
import org.hyperledger.besu.controller.GasLimitCalculator;
import org.hyperledger.besu.crypto.SECP256K1.KeyPair;
import org.hyperledger.besu.ethereum.core.InMemoryStorageProvider;
import org.hyperledger.besu.ethereum.core.MiningParametersTestBuilder;
Expand Down Expand Up @@ -66,6 +67,7 @@ public void blockImport() throws IOException {
.dataDirectory(dataDir)
.clock(TestClock.fixed())
.transactionPoolConfiguration(TransactionPoolConfiguration.builder().build())
.targetGasLimit(GasLimitCalculator.DEFAULT)
.build();
final RlpBlockImporter.ImportResult result =
rlpBlockImporter.importBlockchain(source, targetController);
Expand Down Expand Up @@ -105,6 +107,7 @@ public void ibftImport() throws IOException {
.dataDirectory(dataDir)
.clock(TestClock.fixed())
.transactionPoolConfiguration(TransactionPoolConfiguration.builder().build())
.targetGasLimit(GasLimitCalculator.DEFAULT)
.build();
final RlpBlockImporter.ImportResult result =
rlpBlockImporter.importBlockchain(source, controller);
Expand Down
32 changes: 32 additions & 0 deletions besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNotNull;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
Expand Down Expand Up @@ -174,6 +175,7 @@ public void callingBesuCommandWithoutOptionsMustSyncWithDefaultValues() throws E
verify(mockControllerBuilder).miningParameters(miningArg.capture());
verify(mockControllerBuilder).nodePrivateKeyFile(isNotNull());
verify(mockControllerBuilder).storageProvider(storageProviderArgumentCaptor.capture());
verify(mockControllerBuilder).targetGasLimit(isNull());
verify(mockControllerBuilder).build();

assertThat(storageProviderArgumentCaptor.getValue()).isNotNull();
Expand Down Expand Up @@ -2751,4 +2753,34 @@ public void tomlThatHasInvalidOptions() throws IOException {
assertThat(commandErrorOutput.toString())
.contains("Unknown options in TOML configuration file: invalid_option, invalid_option2");
}

@Test
public void targetGasLimitIsEnabledWhenSpecified() throws Exception {
parseCommand("--target-gas-limit=10000000");

final ArgumentCaptor<Long> targetGasLimitArg = ArgumentCaptor.forClass(Long.class);

verify(mockControllerBuilder).targetGasLimit(targetGasLimitArg.capture());
verify(mockControllerBuilder).build();

assertThat(commandOutput.toString()).isEmpty();
assertThat(commandErrorOutput.toString()).isEmpty();

assertThat(targetGasLimitArg.getValue()).isEqualTo(10_000_000L);
}

@Test
public void targetGasLimitIsDisabledWhenNotSpecified() throws Exception {
parseCommand();

final ArgumentCaptor<Long> targetGasLimitArg = ArgumentCaptor.forClass(Long.class);

verify(mockControllerBuilder).targetGasLimit(targetGasLimitArg.capture());
verify(mockControllerBuilder).build();

assertThat(commandOutput.toString()).isEmpty();
assertThat(commandErrorOutput.toString()).isEmpty();

assertThat(targetGasLimitArg.getValue()).isEqualTo(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ public void initMocks() throws Exception {
when(mockControllerBuilder.isPruningEnabled(anyBoolean())).thenReturn(mockControllerBuilder);
when(mockControllerBuilder.pruningConfiguration(any())).thenReturn(mockControllerBuilder);
when(mockControllerBuilder.genesisConfigOverrides(any())).thenReturn(mockControllerBuilder);
when(mockControllerBuilder.targetGasLimit(any())).thenReturn(mockControllerBuilder);

// doReturn used because of generic BesuController
doReturn(mockController).when(mockControllerBuilder).build();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2019 ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.hyperledger.besu.controller;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.function.Function;

import org.junit.Test;

public class GasLimitCalculatorTest {
cfelde marked this conversation as resolved.
Show resolved Hide resolved
@Test
public void verifyGasLimitIsIncreasedWithinLimits() {
Function<Long, Long> gasLimitCalculator = new GasLimitCalculator(10_000_000L);
assertThat(gasLimitCalculator.apply(8_000_000L))
.isEqualTo(8_000_000L + GasLimitCalculator.ADJUSTMENT_FACTOR);
}

@Test
public void verifyGasLimitIsDecreasedWithinLimits() {
Function<Long, Long> gasLimitCalculator = new GasLimitCalculator(10_000_000L);
assertThat(gasLimitCalculator.apply(12_000_000L))
.isEqualTo(12_000_000L - GasLimitCalculator.ADJUSTMENT_FACTOR);
}

@Test
public void verifyGasLimitReachesTarget() {
final long target = 10_000_000L;
final long offset = GasLimitCalculator.ADJUSTMENT_FACTOR / 2;
Function<Long, Long> gasLimitCalculator = new GasLimitCalculator(target);
assertThat(gasLimitCalculator.apply(target - offset)).isEqualTo(target);
assertThat(gasLimitCalculator.apply(target + offset)).isEqualTo(target);
}
}
3 changes: 3 additions & 0 deletions besu/src/test/resources/everything_config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,6 @@ revert-reason-enabled=false

# Storage plugin to use
key-value-storage="rocksdb"

# Gas limit
target-gas-limit=8000000
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,16 @@ public CliqueMinerExecutor(
final KeyPair nodeKeys,
final MiningParameters miningParams,
final AbstractBlockScheduler blockScheduler,
final EpochManager epochManager) {
final EpochManager epochManager,
final Function<Long, Long> gasLimitCalculator) {
super(
protocolContext,
executorService,
protocolSchedule,
pendingTransactions,
miningParams,
blockScheduler);
blockScheduler,
gasLimitCalculator);
this.nodeKeys = nodeKeys;
this.localAddress = Util.publicKeyToAddress(nodeKeys.getPublicKey());
this.epochManager = epochManager;
Expand Down Expand Up @@ -89,7 +91,7 @@ private CliqueBlockMiner createMiner(
pendingTransactions,
protocolContext,
protocolSchedule,
(gasLimit) -> gasLimit,
gasLimitCalculator,
nodeKeys,
minTransactionGasPrice,
header,
Expand Down
Loading