Skip to content

Commit

Permalink
Qbft migration protocol schedule (hyperledger#3069)
Browse files Browse the repository at this point in the history
Signed-off-by: Jason Frame <jasonwframe@gmail.com>
  • Loading branch information
jframe authored Nov 22, 2021
1 parent d465164 commit c950db4
Show file tree
Hide file tree
Showing 6 changed files with 312 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
package org.hyperledger.besu.controller;

import org.hyperledger.besu.config.GenesisConfigFile;
import org.hyperledger.besu.consensus.common.CombinedProtocolScheduleFactory;
import org.hyperledger.besu.consensus.common.ForkSpec;
import org.hyperledger.besu.consensus.qbft.pki.PkiBlockCreationConfiguration;
import org.hyperledger.besu.crypto.NodeKey;
import org.hyperledger.besu.datatypes.Hash;
Expand Down Expand Up @@ -57,7 +59,11 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Optional;
import java.util.TreeSet;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
Expand All @@ -69,19 +75,35 @@
public class ConsensusScheduleBesuControllerBuilder extends BesuControllerBuilder {

private final Map<Long, BesuControllerBuilder> besuControllerBuilderSchedule = new HashMap<>();
private final BiFunction<
NavigableSet<ForkSpec<ProtocolSchedule>>, Optional<BigInteger>, ProtocolSchedule>
combinedProtocolScheduleFactory;

public ConsensusScheduleBesuControllerBuilder(
final Map<Long, BesuControllerBuilder> besuControllerBuilderSchedule) {
this(
besuControllerBuilderSchedule,
(protocolScheduleSpecs, chainId) ->
new CombinedProtocolScheduleFactory().create(protocolScheduleSpecs, chainId));
}

@VisibleForTesting
protected ConsensusScheduleBesuControllerBuilder(
final Map<Long, BesuControllerBuilder> besuControllerBuilderSchedule,
final BiFunction<
NavigableSet<ForkSpec<ProtocolSchedule>>, Optional<BigInteger>, ProtocolSchedule>
combinedProtocolScheduleFactory) {
Preconditions.checkNotNull(
besuControllerBuilderSchedule, "BesuControllerBuilder schedule can't be null");
Preconditions.checkArgument(
!besuControllerBuilderSchedule.isEmpty(), "BesuControllerBuilder schedule can't be empty");
this.besuControllerBuilderSchedule.putAll(besuControllerBuilderSchedule);
this.combinedProtocolScheduleFactory = combinedProtocolScheduleFactory;
}

@Override
protected void prepForBuild() {
besuControllerBuilderSchedule.get(0L).prepForBuild();
besuControllerBuilderSchedule.values().forEach(BesuControllerBuilder::prepForBuild);
}

@Override
Expand All @@ -105,7 +127,12 @@ protected MiningCoordinator createMiningCoordinator(

@Override
protected ProtocolSchedule createProtocolSchedule() {
return besuControllerBuilderSchedule.get(0L).createProtocolSchedule();
final NavigableSet<ForkSpec<ProtocolSchedule>> protocolScheduleSpecs =
besuControllerBuilderSchedule.entrySet().stream()
.map(e -> new ForkSpec<>(e.getKey(), e.getValue().createProtocolSchedule()))
.collect(Collectors.toCollection(() -> new TreeSet<>(ForkSpec.COMPARATOR)));
final Optional<BigInteger> chainId = genesisConfig.getConfigOptions().getChainId();
return combinedProtocolScheduleFactory.apply(protocolScheduleSpecs, chainId);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,43 @@
*
* SPDX-License-Identifier: Apache-2.0
*/

package org.hyperledger.besu.controller;

import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.when;

import org.hyperledger.besu.config.GenesisConfigFile;
import org.hyperledger.besu.config.StubGenesisConfigOptions;
import org.hyperledger.besu.consensus.common.ForkSpec;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;

import java.math.BigInteger;
import java.util.Collections;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Optional;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.BiFunction;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class ConsensusScheduleBesuControllerBuilderTest {
private @Mock BiFunction<
NavigableSet<ForkSpec<ProtocolSchedule>>, Optional<BigInteger>, ProtocolSchedule>
combinedProtocolScheduleFactory;
private @Mock GenesisConfigFile genesisConfigFile;
private @Mock BesuControllerBuilder besuControllerBuilder1;
private @Mock BesuControllerBuilder besuControllerBuilder2;
private @Mock BesuControllerBuilder besuControllerBuilder3;
private @Mock ProtocolSchedule protocolSchedule1;
private @Mock ProtocolSchedule protocolSchedule2;
private @Mock ProtocolSchedule protocolSchedule3;

@Test
public void mustProvideNonNullConsensusScheduleWhenInstantiatingNew() {
Expand All @@ -40,5 +67,35 @@ public void mustProvideNonEmptyConsensusScheduleWhenInstantiatingNew() {
.hasMessage("BesuControllerBuilder schedule can't be empty");
}

// PSA: Not adding more tests because this is just a skeleton class at this point
@Test
public void mustCreateCombinedProtocolScheduleUsingProtocolSchedulesOrderedByBlock() {
// Use an ordered map with keys in the incorrect order so that we can show that set is created
// with the correct order
final Map<Long, BesuControllerBuilder> besuControllerBuilderSchedule = new TreeMap<>();
besuControllerBuilderSchedule.put(30L, besuControllerBuilder3);
besuControllerBuilderSchedule.put(10L, besuControllerBuilder2);
besuControllerBuilderSchedule.put(0L, besuControllerBuilder1);

when(besuControllerBuilder1.createProtocolSchedule()).thenReturn(protocolSchedule1);
when(besuControllerBuilder2.createProtocolSchedule()).thenReturn(protocolSchedule2);
when(besuControllerBuilder3.createProtocolSchedule()).thenReturn(protocolSchedule3);

final StubGenesisConfigOptions genesisConfigOptions = new StubGenesisConfigOptions();
genesisConfigOptions.chainId(BigInteger.TEN);
when(genesisConfigFile.getConfigOptions()).thenReturn(genesisConfigOptions);

final ConsensusScheduleBesuControllerBuilder consensusScheduleBesuControllerBuilder =
new ConsensusScheduleBesuControllerBuilder(
besuControllerBuilderSchedule, combinedProtocolScheduleFactory);
consensusScheduleBesuControllerBuilder.genesisConfigFile(genesisConfigFile);
consensusScheduleBesuControllerBuilder.createProtocolSchedule();

final NavigableSet<ForkSpec<ProtocolSchedule>> expectedProtocolSchedulesSpecs =
new TreeSet<>(ForkSpec.COMPARATOR);
expectedProtocolSchedulesSpecs.add(new ForkSpec<>(0L, protocolSchedule1));
expectedProtocolSchedulesSpecs.add(new ForkSpec<>(10L, protocolSchedule2));
expectedProtocolSchedulesSpecs.add(new ForkSpec<>(30L, protocolSchedule3));
Mockito.verify(combinedProtocolScheduleFactory)
.apply(expectedProtocolSchedulesSpecs, Optional.of(BigInteger.TEN));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* 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.common;

import static com.google.common.base.Preconditions.checkState;

import org.hyperledger.besu.ethereum.mainnet.MutableProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ScheduledProtocolSpec;

import java.math.BigInteger;
import java.util.NavigableSet;
import java.util.Optional;
import java.util.function.Predicate;

public class CombinedProtocolScheduleFactory {

public ProtocolSchedule create(
final NavigableSet<ForkSpec<ProtocolSchedule>> forkSpecs,
final Optional<BigInteger> chainId) {
final MutableProtocolSchedule combinedProtocolSchedule = new MutableProtocolSchedule(chainId);
for (ForkSpec<ProtocolSchedule> spec : forkSpecs) {
checkState(
spec.getValue() instanceof MutableProtocolSchedule,
"Consensus migration requires a MutableProtocolSchedule");
final MutableProtocolSchedule protocolSchedule = (MutableProtocolSchedule) spec.getValue();

final Optional<Long> endBlock =
Optional.ofNullable(forkSpecs.higher(spec)).map(ForkSpec::getBlock);
protocolSchedule.getScheduledProtocolSpecs().stream()
.filter(protocolSpecMatchesConsensusBlockRange(spec.getBlock(), endBlock))
.forEach(s -> combinedProtocolSchedule.putMilestone(s.getBlock(), s.getSpec()));

// When moving to a new consensus mechanism we want to use the last milestone but created by
// our consensus mechanism's BesuControllerBuilder so any additional rules are applied
if (spec.getBlock() > 0) {
combinedProtocolSchedule.putMilestone(
spec.getBlock(), protocolSchedule.getByBlockNumber(spec.getBlock()));
}
}
return combinedProtocolSchedule;
}

private Predicate<ScheduledProtocolSpec> protocolSpecMatchesConsensusBlockRange(
final long startBlock, final Optional<Long> endBlock) {
return scheduledProtocolSpec ->
scheduledProtocolSpec.getBlock() >= startBlock
&& endBlock.map(b -> scheduledProtocolSpec.getBlock() < b).orElse(true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@
*/
package org.hyperledger.besu.consensus.common;

import java.util.Comparator;
import java.util.Objects;

public class ForkSpec<C> {

public static final Comparator<ForkSpec<?>> COMPARATOR = Comparator.comparing(ForkSpec::getBlock);

private final long block;
private final C value;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* 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.common;

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

import org.hyperledger.besu.config.GenesisConfigOptions;
import org.hyperledger.besu.config.StubGenesisConfigOptions;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters;
import org.hyperledger.besu.evm.internal.EvmConfiguration;

import java.math.BigInteger;
import java.util.List;
import java.util.NavigableSet;
import java.util.Optional;
import java.util.TreeSet;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class CombinedProtocolScheduleFactoryTest {

private final CombinedProtocolScheduleFactory combinedProtocolScheduleFactory =
new CombinedProtocolScheduleFactory();

@Test
public void createsCombinedProtocolScheduleWithMilestonesFromSingleProtocolSchedule() {
final StubGenesisConfigOptions genesisConfigOptions = new StubGenesisConfigOptions();
genesisConfigOptions.homesteadBlock(5L);
genesisConfigOptions.constantinopleBlock(10L);
genesisConfigOptions.chainId(BigInteger.TEN);
final ProtocolSchedule protocolSchedule = createProtocolSchedule(genesisConfigOptions);

final NavigableSet<ForkSpec<ProtocolSchedule>> consensusSchedule =
new TreeSet<>(ForkSpec.COMPARATOR);
consensusSchedule.add(new ForkSpec<>(0, protocolSchedule));

final ProtocolSchedule combinedProtocolSchedule =
combinedProtocolScheduleFactory.create(consensusSchedule, Optional.of(BigInteger.TEN));

assertThat(combinedProtocolSchedule.getByBlockNumber(0L).getName()).isEqualTo("Frontier");
assertThat(combinedProtocolSchedule.getByBlockNumber(0L))
.isSameAs(protocolSchedule.getByBlockNumber(0L));

assertThat(combinedProtocolSchedule.getByBlockNumber(5L).getName()).isEqualTo("Homestead");
assertThat(combinedProtocolSchedule.getByBlockNumber(5L))
.isSameAs(protocolSchedule.getByBlockNumber(5L));

assertThat(combinedProtocolSchedule.getByBlockNumber(10L).getName())
.isEqualTo("Constantinople");
assertThat(combinedProtocolSchedule.getByBlockNumber(10L))
.isSameAs(protocolSchedule.getByBlockNumber(10L));

assertThat(combinedProtocolSchedule.streamMilestoneBlocks().collect(Collectors.toList()))
.isEqualTo(List.of(0L, 5L, 10L));
}

@Test
public void createsCombinedProtocolScheduleWithMilestonesFromMultipleSchedules() {
final StubGenesisConfigOptions genesisConfigOptions = new StubGenesisConfigOptions();
genesisConfigOptions.homesteadBlock(5L);
genesisConfigOptions.constantinopleBlock(10L);
genesisConfigOptions.byzantiumBlock(105L);
genesisConfigOptions.berlinBlock(110L);
genesisConfigOptions.londonBlock(220L);
genesisConfigOptions.chainId(BigInteger.TEN);

final ProtocolSchedule protocolSchedule1 = createProtocolSchedule(genesisConfigOptions);
final ProtocolSchedule protocolSchedule2 = createProtocolSchedule(genesisConfigOptions);
final ProtocolSchedule protocolSchedule3 = createProtocolSchedule(genesisConfigOptions);

final NavigableSet<ForkSpec<ProtocolSchedule>> consensusSchedule =
new TreeSet<>(ForkSpec.COMPARATOR);
consensusSchedule.add(new ForkSpec<>(0, protocolSchedule1));
consensusSchedule.add(new ForkSpec<>(100L, protocolSchedule2));
consensusSchedule.add(new ForkSpec<>(200L, protocolSchedule3));

final ProtocolSchedule combinedProtocolSchedule =
combinedProtocolScheduleFactory.create(consensusSchedule, Optional.of(BigInteger.TEN));

// consensus schedule 1
assertThat(combinedProtocolSchedule.getByBlockNumber(0L).getName()).isEqualTo("Frontier");
assertThat(combinedProtocolSchedule.getByBlockNumber(0L))
.isSameAs(protocolSchedule1.getByBlockNumber(0L));

assertThat(combinedProtocolSchedule.getByBlockNumber(5L).getName()).isEqualTo("Homestead");
assertThat(combinedProtocolSchedule.getByBlockNumber(5L))
.isSameAs(protocolSchedule1.getByBlockNumber(5L));
assertThat(combinedProtocolSchedule.getByBlockNumber(10L).getName())
.isEqualTo("Constantinople");
assertThat(combinedProtocolSchedule.getByBlockNumber(10L))
.isSameAs(protocolSchedule1.getByBlockNumber(10L));

// consensus schedule 2 migration block
assertThat(combinedProtocolSchedule.getByBlockNumber(100L).getName())
.isEqualTo("Constantinople");
assertThat(combinedProtocolSchedule.getByBlockNumber(100L))
.isSameAs(protocolSchedule2.getByBlockNumber(10L));

// consensus schedule 2
assertThat(combinedProtocolSchedule.getByBlockNumber(105L).getName()).isEqualTo("Byzantium");
assertThat(combinedProtocolSchedule.getByBlockNumber(105L))
.isSameAs(protocolSchedule2.getByBlockNumber(105L));
assertThat(combinedProtocolSchedule.getByBlockNumber(110L).getName()).isEqualTo("Berlin");
assertThat(combinedProtocolSchedule.getByBlockNumber(110L))
.isSameAs(protocolSchedule2.getByBlockNumber(110L));

// consensus schedule 3 migration block
assertThat(combinedProtocolSchedule.getByBlockNumber(200L).getName()).isEqualTo("Berlin");
assertThat(combinedProtocolSchedule.getByBlockNumber(200L))
.isSameAs(protocolSchedule3.getByBlockNumber(110L));

// consensus schedule 3
assertThat(combinedProtocolSchedule.getByBlockNumber(220L).getName()).isEqualTo("London");
assertThat(combinedProtocolSchedule.getByBlockNumber(220L))
.isSameAs(protocolSchedule3.getByBlockNumber(220L));

assertThat(combinedProtocolSchedule.streamMilestoneBlocks().collect(Collectors.toList()))
.isEqualTo(List.of(0L, 5L, 10L, 100L, 105L, 110L, 200L, 220L));
}

private ProtocolSchedule createProtocolSchedule(final GenesisConfigOptions genesisConfigOptions) {
final ProtocolScheduleBuilder protocolScheduleBuilder =
new ProtocolScheduleBuilder(
genesisConfigOptions,
BigInteger.ONE,
ProtocolSpecAdapters.create(0, Function.identity()),
new PrivacyParameters(),
false,
false,
EvmConfiguration.DEFAULT);

return protocolScheduleBuilder.createProtocolSchedule();
}
}
Loading

0 comments on commit c950db4

Please sign in to comment.