Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
# Changelog

## 24.2.0-SNAPSHOT
## Next Release

### Breaking Changes

### Additions and Improvements
- Add bft-style blockperiodseconds transitions to Clique [#6596](https://github.com/hyperledger/besu/pull/6596)

### Bug fixes

### Download Links

## 24.2.0

### Breaking Changes
- SNAP - Snap sync is now the default for named networks [#6530](https://github.com/hyperledger/besu/pull/6530)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,23 @@
*/
package org.hyperledger.besu.tests.acceptance.clique;

import static java.util.stream.Collectors.joining;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.assertj.core.data.Percentage.withPercentage;

import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBaseJunit5;
import org.hyperledger.besu.tests.acceptance.dsl.account.Account;
import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode;
import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.genesis.GenesisConfigurationFactory.CliqueOptions;

import java.io.IOException;
import java.math.BigInteger;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.junit.jupiter.api.Test;
import org.web3j.protocol.core.DefaultBlockParameter;

public class CliqueMiningAcceptanceTest extends AcceptanceTestBaseJunit5 {

Expand Down Expand Up @@ -123,4 +132,117 @@ public void shouldStillMineWhenANodeFailsAndHasSufficientValidators() throws IOE
cluster.verifyOnActiveNodes(clique.blockIsCreatedByProposer(minerNode1));
cluster.verifyOnActiveNodes(clique.blockIsCreatedByProposer(minerNode2));
}

@Test
public void shouldMineBlocksWithBlockPeriodAccordingToTransitions() throws IOException {

final var cliqueOptions = new CliqueOptions(3, CliqueOptions.DEFAULT.epochLength(), true);
final BesuNode minerNode = besu.createCliqueNode("miner1", cliqueOptions);

// setup transitions
final Map<String, Object> decreasePeriodTo2_Transition =
Map.of("block", 3, "blockperiodseconds", 2);
final Map<String, Object> decreasePeriodTo1_Transition =
Map.of("block", 4, "blockperiodseconds", 1);
final Map<String, Object> increasePeriodTo2_Transition =
Map.of("block", 6, "blockperiodseconds", 2);

final Optional<String> initialGenesis =
minerNode.getGenesisConfigProvider().create(List.of(minerNode));
final String genesisWithTransitions =
prependTransitionsToCliqueOptions(
initialGenesis.orElseThrow(),
List.of(
decreasePeriodTo2_Transition,
decreasePeriodTo1_Transition,
increasePeriodTo2_Transition));
minerNode.setGenesisConfig(genesisWithTransitions);

// Mine 6 blocks
cluster.start(minerNode);
minerNode.verify(blockchain.reachesHeight(minerNode, 5));

// Assert the block period decreased/increased after each transition
final long block1Timestamp = getTimestampForBlock(minerNode, 1);
final long block2Timestamp = getTimestampForBlock(minerNode, 2);
final long block3Timestamp = getTimestampForBlock(minerNode, 3);
final long block4Timestamp = getTimestampForBlock(minerNode, 4);
final long block5Timestamp = getTimestampForBlock(minerNode, 5);
final long block6Timestamp = getTimestampForBlock(minerNode, 6);
assertThat(block2Timestamp - block1Timestamp).isCloseTo(3, withPercentage(20));
assertThat(block3Timestamp - block2Timestamp).isCloseTo(2, withPercentage(20));
assertThat(block4Timestamp - block3Timestamp).isCloseTo(1, withPercentage(20));
assertThat(block5Timestamp - block4Timestamp).isCloseTo(1, withPercentage(20));
assertThat(block6Timestamp - block5Timestamp).isCloseTo(2, withPercentage(20));
}

private long getTimestampForBlock(final BesuNode minerNode, final int blockNumber) {
return minerNode
.execute(
ethTransactions.block(DefaultBlockParameter.valueOf(BigInteger.valueOf(blockNumber))))
.getTimestamp()
.longValue();
}

private String prependTransitionsToCliqueOptions(
final String originalOptions, final List<Map<String, Object>> transitions) {
final StringBuilder stringBuilder =
new StringBuilder()
.append(formatCliqueTransitionsOptions(transitions))
.append(",\n")
.append(quote("clique"))
.append(": {");

return originalOptions.replace(quote("clique") + ": {", stringBuilder.toString());
}

private String formatCliqueTransitionsOptions(final List<Map<String, Object>> transitions) {
final StringBuilder stringBuilder = new StringBuilder();

stringBuilder.append(quote("transitions"));
stringBuilder.append(": {\n");
stringBuilder.append(quote("clique"));
stringBuilder.append(": [");
final String formattedTransitions =
transitions.stream().map(this::formatTransition).collect(joining(",\n"));
stringBuilder.append(formattedTransitions);
stringBuilder.append("\n]");
stringBuilder.append("}\n");

return stringBuilder.toString();
}

private String quote(final Object value) {
return '"' + value.toString() + '"';
}

private String formatTransition(final Map<String, Object> transition) {
final StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("{");
String formattedTransition =
transition.keySet().stream()
.map(key -> formatKeyValues(key, transition.get(key)))
.collect(joining(","));
stringBuilder.append(formattedTransition);
stringBuilder.append("}");
return stringBuilder.toString();
}

private String formatKeyValues(final Object... keyOrValue) {
if (keyOrValue.length % 2 == 1) {
// An odd number of strings cannot form a set of key-value pairs
throw new IllegalArgumentException("Must supply key-value pairs");
}
final StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < keyOrValue.length; i += 2) {
if (i > 0) {
stringBuilder.append(", ");
}
final String key = keyOrValue[i].toString();
final Object value = keyOrValue[i + 1];
final String valueStr = value instanceof String ? quote(value) : value.toString();
stringBuilder.append(String.format("\n%s: %s", quote(key), valueStr));
}
return stringBuilder.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.hyperledger.besu.config.CliqueConfigOptions;
import org.hyperledger.besu.consensus.clique.CliqueBlockInterface;
import org.hyperledger.besu.consensus.clique.CliqueContext;
import org.hyperledger.besu.consensus.clique.CliqueForksSchedulesFactory;
import org.hyperledger.besu.consensus.clique.CliqueMiningTracker;
import org.hyperledger.besu.consensus.clique.CliqueProtocolSchedule;
import org.hyperledger.besu.consensus.clique.blockcreation.CliqueBlockScheduler;
Expand All @@ -27,6 +28,7 @@
import org.hyperledger.besu.consensus.clique.jsonrpc.CliqueJsonRpcMethods;
import org.hyperledger.besu.consensus.common.BlockInterface;
import org.hyperledger.besu.consensus.common.EpochManager;
import org.hyperledger.besu.consensus.common.ForksSchedule;
import org.hyperledger.besu.consensus.common.validator.blockbased.BlockValidatorProvider;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.ethereum.ProtocolContext;
Expand All @@ -52,19 +54,19 @@ public class CliqueBesuControllerBuilder extends BesuControllerBuilder {

private Address localAddress;
private EpochManager epochManager;
private long secondsBetweenBlocks;
private boolean createEmptyBlocks = true;
private final BlockInterface blockInterface = new CliqueBlockInterface();
private ForksSchedule<CliqueConfigOptions> forksSchedule;

@Override
protected void prepForBuild() {
localAddress = Util.publicKeyToAddress(nodeKey.getPublicKey());
final CliqueConfigOptions cliqueConfig = configOptionsSupplier.get().getCliqueConfigOptions();
final long blocksPerEpoch = cliqueConfig.getEpochLength();
secondsBetweenBlocks = cliqueConfig.getBlockPeriodSeconds();
createEmptyBlocks = cliqueConfig.getCreateEmptyBlocks();

epochManager = new EpochManager(blocksPerEpoch);
forksSchedule = CliqueForksSchedulesFactory.create(configOptionsSupplier.get());
}

@Override
Expand Down Expand Up @@ -92,7 +94,7 @@ protected MiningCoordinator createMiningCoordinator(
clock,
protocolContext.getConsensusContext(CliqueContext.class).getValidatorProvider(),
localAddress,
secondsBetweenBlocks),
forksSchedule),
epochManager,
createEmptyBlocks,
ethProtocolManager.ethContext().getScheduler());
Expand All @@ -113,6 +115,7 @@ protected MiningCoordinator createMiningCoordinator(
protected ProtocolSchedule createProtocolSchedule() {
return CliqueProtocolSchedule.create(
configOptionsSupplier.get(),
forksSchedule,
nodeKey,
privacyParameters,
isRevertReasonEnabled,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import org.apache.tuweni.bytes.Bytes;

/** The Bft fork. */
public class BftFork {
public class BftFork implements Fork {

/** The constant FORK_BLOCK_KEY. */
public static final String FORK_BLOCK_KEY = "block";
Expand Down Expand Up @@ -59,6 +59,7 @@ public BftFork(final ObjectNode forkConfigRoot) {
*
* @return the fork block
*/
@Override
public long getForkBlock() {
return JsonUtil.getLong(forkConfigRoot, FORK_BLOCK_KEY)
.orElseThrow(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright ConsenSys AG.
* 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
Expand All @@ -12,75 +12,39 @@
*
* SPDX-License-Identifier: Apache-2.0
*/

package org.hyperledger.besu.config;

import java.util.Map;

import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.ImmutableMap;

/** The Clique config options. */
public class CliqueConfigOptions {

/** The constant DEFAULT. */
public static final CliqueConfigOptions DEFAULT =
new CliqueConfigOptions(JsonUtil.createEmptyObjectNode());

private static final long DEFAULT_EPOCH_LENGTH = 30_000;
private static final int DEFAULT_BLOCK_PERIOD_SECONDS = 15;
private static final boolean DEFAULT_CREATE_EMPTY_BLOCKS = true;

private final ObjectNode cliqueConfigRoot;

/**
* Instantiates a new Clique config options.
*
* @param cliqueConfigRoot the clique config root
*/
CliqueConfigOptions(final ObjectNode cliqueConfigRoot) {
this.cliqueConfigRoot = cliqueConfigRoot;
}
/** Configuration options for the Clique consensus mechanism. */
public interface CliqueConfigOptions {

/**
* The number of blocks in an epoch.
*
* @return the epoch length
*/
public long getEpochLength() {
return JsonUtil.getLong(cliqueConfigRoot, "epochlength", DEFAULT_EPOCH_LENGTH);
}
long getEpochLength();

/**
* Gets block period seconds.
*
* @return the block period seconds
*/
public int getBlockPeriodSeconds() {
return JsonUtil.getPositiveInt(
cliqueConfigRoot, "blockperiodseconds", DEFAULT_BLOCK_PERIOD_SECONDS);
}
int getBlockPeriodSeconds();

/**
* Whether the creation of empty blocks is allowed.
* Gets create empty blocks.
*
* @return the create empty block status
* @return whether empty blocks are permitted
*/
public boolean getCreateEmptyBlocks() {
return JsonUtil.getBoolean(cliqueConfigRoot, "createemptyblocks", DEFAULT_CREATE_EMPTY_BLOCKS);
}
boolean getCreateEmptyBlocks();

/**
* As map.
* A map of the config options.
*
* @return the map
*/
Map<String, Object> asMap() {
return ImmutableMap.of(
"epochLength",
getEpochLength(),
"blockPeriodSeconds",
getBlockPeriodSeconds(),
"createemptyblocks",
getCreateEmptyBlocks());
}
Map<String, Object> asMap();
}
66 changes: 66 additions & 0 deletions config/src/main/java/org/hyperledger/besu/config/CliqueFork.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 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.config;

import java.util.OptionalInt;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.databind.node.ObjectNode;

/** The Clique fork. */
public class CliqueFork implements Fork {

/** The constant FORK_BLOCK_KEY. */
public static final String FORK_BLOCK_KEY = "block";

/** The constant BLOCK_PERIOD_SECONDS_KEY. */
public static final String BLOCK_PERIOD_SECONDS_KEY = "blockperiodseconds";

/** The Fork config root. */
protected final ObjectNode forkConfigRoot;

/**
* Instantiates a new Clique fork.
*
* @param forkConfigRoot the fork config root
*/
@JsonCreator
public CliqueFork(final ObjectNode forkConfigRoot) {
this.forkConfigRoot = forkConfigRoot;
}

/**
* Gets fork block.
*
* @return the fork block
*/
@Override
public long getForkBlock() {
return JsonUtil.getLong(forkConfigRoot, FORK_BLOCK_KEY)
.orElseThrow(
() ->
new IllegalArgumentException(
"Fork block not specified for Clique fork in custom forks"));
}

/**
* Gets block period seconds.
*
* @return the block period seconds
*/
public OptionalInt getBlockPeriodSeconds() {
return JsonUtil.getPositiveInt(forkConfigRoot, BLOCK_PERIOD_SECONDS_KEY);
}
}
Loading