mirror of https://github.com/hyperledger/besu
Bft block period transition (#2902)
Allow block period to be configured using transitions for IBFT2 and QBFT Signed-off-by: Jason Frame <jasonwframe@gmail.com>pull/2918/head
parent
73ddc6aec2
commit
0e0d67dbe5
@ -0,0 +1,76 @@ |
||||
/* |
||||
* 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.consensus.ibft.tests; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
import org.hyperledger.besu.config.BftFork; |
||||
import org.hyperledger.besu.config.JsonUtil; |
||||
import org.hyperledger.besu.consensus.common.bft.BftEventQueue; |
||||
import org.hyperledger.besu.consensus.common.bft.events.NewChainHead; |
||||
import org.hyperledger.besu.consensus.ibft.support.TestContext; |
||||
import org.hyperledger.besu.consensus.ibft.support.TestContextBuilder; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||
import org.hyperledger.besu.testutil.TestClock; |
||||
|
||||
import java.time.Instant; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.concurrent.TimeUnit; |
||||
|
||||
import org.junit.Test; |
||||
|
||||
public class TransitionsTest { |
||||
|
||||
@Test |
||||
public void transitionsBlockPeriod() throws InterruptedException { |
||||
final TestClock clock = new TestClock(Instant.EPOCH); |
||||
|
||||
final List<BftFork> bftForks = |
||||
List.of( |
||||
new BftFork( |
||||
JsonUtil.objectNodeFromMap( |
||||
Map.of(BftFork.FORK_BLOCK_KEY, 1, BftFork.BLOCK_PERIOD_SECONDS_KEY, 10))), |
||||
new BftFork( |
||||
JsonUtil.objectNodeFromMap( |
||||
Map.of(BftFork.FORK_BLOCK_KEY, 2, BftFork.BLOCK_PERIOD_SECONDS_KEY, 20)))); |
||||
|
||||
final BftEventQueue bftEventQueue = new BftEventQueue(TestContextBuilder.MESSAGE_QUEUE_LIMIT); |
||||
final TestContext context = |
||||
new TestContextBuilder() |
||||
.indexOfFirstLocallyProposedBlock(0) |
||||
.validatorCount(1) |
||||
.clock(clock) |
||||
.bftForks(bftForks) |
||||
.eventQueue(bftEventQueue) |
||||
.buildAndStart(); |
||||
|
||||
clock.stepMillis(10_000); |
||||
context.getEventMultiplexer().handleBftEvent(bftEventQueue.poll(1, TimeUnit.SECONDS)); |
||||
|
||||
context |
||||
.getController() |
||||
.handleNewBlockEvent(new NewChainHead(context.getBlockchain().getChainHeadHeader())); |
||||
clock.stepMillis(20_000); |
||||
context.getEventMultiplexer().handleBftEvent(bftEventQueue.poll(1, TimeUnit.SECONDS)); |
||||
|
||||
final BlockHeader genesisBlock = context.getBlockchain().getBlockHeader(0).get(); |
||||
final BlockHeader blockHeader1 = context.getBlockchain().getBlockHeader(1).get(); |
||||
final BlockHeader blockHeader2 = context.getBlockchain().getBlockHeader(2).get(); |
||||
|
||||
assertThat(blockHeader1.getTimestamp()).isEqualTo(genesisBlock.getTimestamp() + 10); |
||||
assertThat(blockHeader2.getTimestamp()).isEqualTo(blockHeader1.getTimestamp() + 20); |
||||
} |
||||
} |
@ -0,0 +1,103 @@ |
||||
/* |
||||
* 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.consensus.ibft; |
||||
|
||||
import static java.util.Collections.singletonList; |
||||
|
||||
import org.hyperledger.besu.consensus.common.bft.BftBlockHeaderFunctions; |
||||
import org.hyperledger.besu.consensus.common.bft.BftExtraData; |
||||
import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec; |
||||
import org.hyperledger.besu.consensus.common.bft.BftExtraDataFixture; |
||||
import org.hyperledger.besu.consensus.common.bft.Vote; |
||||
import org.hyperledger.besu.crypto.NodeKey; |
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; |
||||
import org.hyperledger.besu.ethereum.core.Difficulty; |
||||
import org.hyperledger.besu.ethereum.core.Util; |
||||
|
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
|
||||
public class IbftBlockHeaderUtils { |
||||
|
||||
private static final int ROUND_NUMBER = 0x2A; |
||||
|
||||
@FunctionalInterface |
||||
public interface HeaderModifier { |
||||
|
||||
void update(BlockHeaderTestFixture blockHeaderTestFixture); |
||||
} |
||||
|
||||
public static BlockHeaderTestFixture createPresetHeaderBuilder( |
||||
final long number, |
||||
final NodeKey proposerNodeKey, |
||||
final List<Address> validators, |
||||
final BlockHeader parent) { |
||||
return createPresetHeaderBuilder(number, proposerNodeKey, validators, parent, null); |
||||
} |
||||
|
||||
public static BlockHeaderTestFixture createPresetHeaderBuilder( |
||||
final long number, |
||||
final NodeKey proposerNodeKey, |
||||
final List<Address> validators, |
||||
final BlockHeader parent, |
||||
final HeaderModifier modifier) { |
||||
final BlockHeaderTestFixture builder = new BlockHeaderTestFixture(); |
||||
final IbftExtraDataCodec ibftExtraDataEncoder = new IbftExtraDataCodec(); |
||||
populateDefaultBlockHeader( |
||||
number, proposerNodeKey, parent, modifier, builder, ibftExtraDataEncoder); |
||||
|
||||
final BftExtraData bftExtraData = |
||||
BftExtraDataFixture.createExtraData( |
||||
builder.buildHeader(), |
||||
Bytes.wrap(new byte[BftExtraDataCodec.EXTRA_VANITY_LENGTH]), |
||||
Optional.of(Vote.authVote(Address.fromHexString("1"))), |
||||
validators, |
||||
singletonList(proposerNodeKey), |
||||
ROUND_NUMBER, |
||||
ibftExtraDataEncoder); |
||||
|
||||
builder.extraData(ibftExtraDataEncoder.encode(bftExtraData)); |
||||
return builder; |
||||
} |
||||
|
||||
private static void populateDefaultBlockHeader( |
||||
final long number, |
||||
final NodeKey proposerNodeKey, |
||||
final BlockHeader parent, |
||||
final HeaderModifier modifier, |
||||
final BlockHeaderTestFixture builder, |
||||
final IbftExtraDataCodec ibftExtraDataEncoder) { |
||||
if (parent != null) { |
||||
builder.parentHash(parent.getHash()); |
||||
} |
||||
builder.number(number); |
||||
builder.gasLimit(5000); |
||||
builder.timestamp(6 * number); |
||||
builder.mixHash( |
||||
Hash.fromHexString("0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365")); |
||||
builder.difficulty(Difficulty.ONE); |
||||
builder.coinbase(Util.publicKeyToAddress(proposerNodeKey.getPublicKey())); |
||||
builder.blockHeaderFunctions(BftBlockHeaderFunctions.forCommittedSeal(ibftExtraDataEncoder)); |
||||
|
||||
if (modifier != null) { |
||||
modifier.update(builder); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,124 @@ |
||||
/* |
||||
* 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.consensus.ibft; |
||||
|
||||
import static java.util.Collections.singletonList; |
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat; |
||||
import static org.hyperledger.besu.consensus.common.bft.BftContextBuilder.setupContextWithBftExtraDataEncoder; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
import org.hyperledger.besu.config.BftConfigOptions; |
||||
import org.hyperledger.besu.config.GenesisConfigOptions; |
||||
import org.hyperledger.besu.config.JsonGenesisConfigOptions; |
||||
import org.hyperledger.besu.config.JsonQbftConfigOptions; |
||||
import org.hyperledger.besu.config.JsonUtil; |
||||
import org.hyperledger.besu.consensus.common.bft.BftContext; |
||||
import org.hyperledger.besu.consensus.common.bft.BftExtraData; |
||||
import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec; |
||||
import org.hyperledger.besu.consensus.common.bft.BftForkSpec; |
||||
import org.hyperledger.besu.consensus.common.bft.BftForksSchedule; |
||||
import org.hyperledger.besu.consensus.common.bft.MutableBftConfigOptions; |
||||
import org.hyperledger.besu.crypto.NodeKey; |
||||
import org.hyperledger.besu.crypto.NodeKeyUtils; |
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.ethereum.ProtocolContext; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||
import org.hyperledger.besu.ethereum.core.PrivacyParameters; |
||||
import org.hyperledger.besu.ethereum.core.Util; |
||||
import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; |
||||
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; |
||||
import org.hyperledger.besu.evm.internal.EvmConfiguration; |
||||
|
||||
import java.math.BigInteger; |
||||
import java.util.Collection; |
||||
import java.util.List; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
|
||||
public class IbftProtocolScheduleTest { |
||||
private final BftExtraDataCodec bftExtraDataCodec = mock(BftExtraDataCodec.class); |
||||
private final BftExtraData bftExtraData = mock(BftExtraData.class); |
||||
private final NodeKey proposerNodeKey = NodeKeyUtils.generate(); |
||||
private final Address proposerAddress = Util.publicKeyToAddress(proposerNodeKey.getPublicKey()); |
||||
private final List<Address> validators = singletonList(proposerAddress); |
||||
|
||||
@Before |
||||
public void setup() { |
||||
when(bftExtraDataCodec.decode(any())).thenReturn(bftExtraData); |
||||
when(bftExtraData.getValidators()).thenReturn(validators); |
||||
} |
||||
|
||||
@Test |
||||
public void blockModeTransitionsCreatesBlockModeHeaderValidators() { |
||||
final MutableBftConfigOptions arbitraryTransition = |
||||
new MutableBftConfigOptions(JsonQbftConfigOptions.DEFAULT); |
||||
arbitraryTransition.setBlockRewardWei(BigInteger.ONE); |
||||
|
||||
final BlockHeader parentHeader = |
||||
IbftBlockHeaderUtils.createPresetHeaderBuilder(1, proposerNodeKey, validators, null) |
||||
.buildHeader(); |
||||
final BlockHeader blockHeader = |
||||
IbftBlockHeaderUtils.createPresetHeaderBuilder(2, proposerNodeKey, validators, parentHeader) |
||||
.buildHeader(); |
||||
|
||||
final ProtocolSchedule schedule = |
||||
createProtocolSchedule( |
||||
JsonGenesisConfigOptions.fromJsonObject(JsonUtil.createEmptyObjectNode()), |
||||
new BftForkSpec<>(0, JsonQbftConfigOptions.DEFAULT), |
||||
List.of( |
||||
new BftForkSpec<>(1, arbitraryTransition), |
||||
new BftForkSpec<>(2, JsonQbftConfigOptions.DEFAULT))); |
||||
assertThat(schedule.streamMilestoneBlocks().count()).isEqualTo(3); |
||||
assertThat(validateHeader(schedule, validators, parentHeader, blockHeader, 0)).isTrue(); |
||||
assertThat(validateHeader(schedule, validators, parentHeader, blockHeader, 1)).isTrue(); |
||||
assertThat(validateHeader(schedule, validators, parentHeader, blockHeader, 2)).isTrue(); |
||||
} |
||||
|
||||
private ProtocolSchedule createProtocolSchedule( |
||||
final GenesisConfigOptions genesisConfig, |
||||
final BftForkSpec<BftConfigOptions> genesisFork, |
||||
final List<BftForkSpec<BftConfigOptions>> forks) { |
||||
return IbftProtocolSchedule.create( |
||||
genesisConfig, |
||||
new BftForksSchedule<>(genesisFork, forks), |
||||
PrivacyParameters.DEFAULT, |
||||
false, |
||||
bftExtraDataCodec, |
||||
EvmConfiguration.DEFAULT); |
||||
} |
||||
|
||||
private boolean validateHeader( |
||||
final ProtocolSchedule schedule, |
||||
final List<Address> validators, |
||||
final BlockHeader parentHeader, |
||||
final BlockHeader blockHeader, |
||||
final int block) { |
||||
return schedule |
||||
.getByBlockNumber(block) |
||||
.getBlockHeaderValidator() |
||||
.validateHeader( |
||||
blockHeader, parentHeader, protocolContext(validators), HeaderValidationMode.LIGHT); |
||||
} |
||||
|
||||
private ProtocolContext protocolContext(final Collection<Address> validators) { |
||||
return new ProtocolContext( |
||||
null, |
||||
null, |
||||
setupContextWithBftExtraDataEncoder(BftContext.class, validators, bftExtraDataCodec)); |
||||
} |
||||
} |
@ -0,0 +1,77 @@ |
||||
/* |
||||
* 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.consensus.qbft.test; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
import org.hyperledger.besu.config.BftFork; |
||||
import org.hyperledger.besu.config.JsonUtil; |
||||
import org.hyperledger.besu.config.QbftFork; |
||||
import org.hyperledger.besu.consensus.common.bft.BftEventQueue; |
||||
import org.hyperledger.besu.consensus.common.bft.events.NewChainHead; |
||||
import org.hyperledger.besu.consensus.qbft.support.TestContext; |
||||
import org.hyperledger.besu.consensus.qbft.support.TestContextBuilder; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||
import org.hyperledger.besu.testutil.TestClock; |
||||
|
||||
import java.time.Instant; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.concurrent.TimeUnit; |
||||
|
||||
import org.junit.Test; |
||||
|
||||
public class TransitionsTest { |
||||
|
||||
@Test |
||||
public void transitionsBlockPeriod() throws InterruptedException { |
||||
final TestClock clock = new TestClock(Instant.EPOCH); |
||||
|
||||
final List<QbftFork> qbftForks = |
||||
List.of( |
||||
new QbftFork( |
||||
JsonUtil.objectNodeFromMap( |
||||
Map.of(BftFork.FORK_BLOCK_KEY, 1, BftFork.BLOCK_PERIOD_SECONDS_KEY, 10))), |
||||
new QbftFork( |
||||
JsonUtil.objectNodeFromMap( |
||||
Map.of(BftFork.FORK_BLOCK_KEY, 2, BftFork.BLOCK_PERIOD_SECONDS_KEY, 20)))); |
||||
|
||||
final BftEventQueue bftEventQueue = new BftEventQueue(TestContextBuilder.MESSAGE_QUEUE_LIMIT); |
||||
final TestContext context = |
||||
new TestContextBuilder() |
||||
.indexOfFirstLocallyProposedBlock(0) |
||||
.validatorCount(1) |
||||
.clock(clock) |
||||
.qbftForks(qbftForks) |
||||
.eventQueue(bftEventQueue) |
||||
.buildAndStart(); |
||||
|
||||
clock.stepMillis(10_000); |
||||
context.getEventMultiplexer().handleBftEvent(bftEventQueue.poll(1, TimeUnit.SECONDS)); |
||||
|
||||
context |
||||
.getController() |
||||
.handleNewBlockEvent(new NewChainHead(context.getBlockchain().getChainHeadHeader())); |
||||
clock.stepMillis(20_000); |
||||
context.getEventMultiplexer().handleBftEvent(bftEventQueue.poll(1, TimeUnit.SECONDS)); |
||||
|
||||
final BlockHeader genesisBlock = context.getBlockchain().getBlockHeader(0).get(); |
||||
final BlockHeader blockHeader1 = context.getBlockchain().getBlockHeader(1).get(); |
||||
final BlockHeader blockHeader2 = context.getBlockchain().getBlockHeader(2).get(); |
||||
|
||||
assertThat(blockHeader1.getTimestamp()).isEqualTo(genesisBlock.getTimestamp() + 10); |
||||
assertThat(blockHeader2.getTimestamp()).isEqualTo(blockHeader1.getTimestamp() + 20); |
||||
} |
||||
} |
@ -0,0 +1,16 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<Configuration status="WARN"> |
||||
<Properties> |
||||
<Property name="root.log.level">DEBUG</Property> |
||||
</Properties> |
||||
|
||||
<Appenders> |
||||
<Console name="Console" target="SYSTEM_OUT"> |
||||
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSSZZZ} | %t | %-5level | %c{1} | %msg%n" /> </Console> |
||||
</Appenders> |
||||
<Loggers> |
||||
<Root level="${sys:root.log.level}"> |
||||
<AppenderRef ref="Console" /> |
||||
</Root> |
||||
</Loggers> |
||||
</Configuration> |
Loading…
Reference in new issue