Qbft to use validator contract (#2574)

Signed-off-by: Jason Frame <jasonwframe@gmail.com>
This commit is contained in:
Jason Frame
2021-08-05 12:11:29 +10:00
committed by GitHub
parent 55144320e3
commit 47a3941467
21 changed files with 880 additions and 55 deletions

View File

@@ -253,7 +253,11 @@ public abstract class BesuControllerBuilder {
createWorldStateArchive(worldStateStorage, blockchain);
final ProtocolContext protocolContext =
ProtocolContext.init(
blockchain, worldStateArchive, genesisState, this::createConsensusContext);
blockchain,
worldStateArchive,
genesisState,
protocolSchedule,
this::createConsensusContext);
validateContext(protocolContext);
protocolSchedule.setPublicWorldStateArchiveForPrivacyBlockProcessor(
@@ -408,7 +412,9 @@ public abstract class BesuControllerBuilder {
protected void validateContext(final ProtocolContext context) {}
protected abstract Object createConsensusContext(
Blockchain blockchain, WorldStateArchive worldStateArchive);
Blockchain blockchain,
WorldStateArchive worldStateArchive,
ProtocolSchedule protocolSchedule);
protected String getSupportedProtocol() {
return EthProtocol.NAME;

View File

@@ -130,7 +130,9 @@ public class CliqueBesuControllerBuilder extends BesuControllerBuilder {
@Override
protected CliqueContext createConsensusContext(
final Blockchain blockchain, final WorldStateArchive worldStateArchive) {
final Blockchain blockchain,
final WorldStateArchive worldStateArchive,
final ProtocolSchedule protocolSchedule) {
return new CliqueContext(
BlockValidatorProvider.nonForkingValidatorProvider(
blockchain, epochManager, blockInterface),

View File

@@ -17,6 +17,7 @@ package org.hyperledger.besu.controller;
import org.hyperledger.besu.config.BftConfigOptions;
import org.hyperledger.besu.config.BftFork;
import org.hyperledger.besu.config.GenesisConfigOptions;
import org.hyperledger.besu.consensus.common.BftValidatorOverrides;
import org.hyperledger.besu.consensus.common.EpochManager;
import org.hyperledger.besu.consensus.common.bft.BftContext;
import org.hyperledger.besu.consensus.common.bft.BftEventQueue;
@@ -249,23 +250,25 @@ public class IbftBesuControllerBuilder extends BftBesuControllerBuilder {
@Override
protected BftContext createConsensusContext(
final Blockchain blockchain, final WorldStateArchive worldStateArchive) {
final Blockchain blockchain,
final WorldStateArchive worldStateArchive,
final ProtocolSchedule protocolSchedule) {
final GenesisConfigOptions configOptions =
genesisConfig.getConfigOptions(genesisConfigOverrides);
final BftConfigOptions ibftConfig = configOptions.getBftConfigOptions();
final EpochManager epochManager = new EpochManager(ibftConfig.getEpochLength());
final Map<Long, List<Address>> bftValidatorForkMap =
final BftValidatorOverrides validatorOverrides =
convertIbftForks(configOptions.getTransitions().getIbftForks());
return new BftContext(
BlockValidatorProvider.forkingValidatorProvider(
blockchain, epochManager, bftBlockInterface().get(), bftValidatorForkMap),
blockchain, epochManager, bftBlockInterface().get(), validatorOverrides),
epochManager,
bftBlockInterface().get());
}
private Map<Long, List<Address>> convertIbftForks(final List<BftFork> bftForks) {
private BftValidatorOverrides convertIbftForks(final List<BftFork> bftForks) {
final Map<Long, List<Address>> result = new HashMap<>();
for (final BftFork fork : bftForks) {
@@ -279,7 +282,7 @@ public class IbftBesuControllerBuilder extends BftBesuControllerBuilder {
.collect(Collectors.toList())));
}
return result;
return new BftValidatorOverrides(result);
}
private static MinedBlockObserver blockLogger(

View File

@@ -81,7 +81,9 @@ public class IbftLegacyBesuControllerBuilder extends BesuControllerBuilder {
@Override
protected IbftLegacyContext createConsensusContext(
final Blockchain blockchain, final WorldStateArchive worldStateArchive) {
final Blockchain blockchain,
final WorldStateArchive worldStateArchive,
final ProtocolSchedule protocolSchedule) {
final IbftLegacyConfigOptions ibftConfig =
genesisConfig.getConfigOptions(genesisConfigOverrides).getIbftLegacyConfigOptions();
final EpochManager epochManager = new EpochManager(ibftConfig.getEpochLength());

View File

@@ -76,7 +76,9 @@ public class MainnetBesuControllerBuilder extends BesuControllerBuilder {
@Override
protected Void createConsensusContext(
final Blockchain blockchain, final WorldStateArchive worldStateArchive) {
final Blockchain blockchain,
final WorldStateArchive worldStateArchive,
final ProtocolSchedule protocolSchedule) {
return null;
}

View File

@@ -14,10 +14,10 @@
*/
package org.hyperledger.besu.controller;
import org.hyperledger.besu.config.BftConfigOptions;
import org.hyperledger.besu.config.BftFork;
import org.hyperledger.besu.config.GenesisConfigOptions;
import org.hyperledger.besu.config.QbftConfigOptions;
import org.hyperledger.besu.consensus.common.BftValidatorOverrides;
import org.hyperledger.besu.consensus.common.EpochManager;
import org.hyperledger.besu.consensus.common.bft.BftContext;
import org.hyperledger.besu.consensus.common.bft.BftEventQueue;
@@ -53,6 +53,8 @@ import org.hyperledger.besu.consensus.qbft.statemachine.QbftBlockHeightManagerFa
import org.hyperledger.besu.consensus.qbft.statemachine.QbftController;
import org.hyperledger.besu.consensus.qbft.statemachine.QbftRoundFactory;
import org.hyperledger.besu.consensus.qbft.validation.MessageValidatorFactory;
import org.hyperledger.besu.consensus.qbft.validator.TransactionValidatorProvider;
import org.hyperledger.besu.consensus.qbft.validator.ValidatorContractController;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.methods.JsonRpcMethods;
import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator;
@@ -69,6 +71,7 @@ import org.hyperledger.besu.ethereum.eth.sync.state.SyncState;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.p2p.config.SubProtocolConfiguration;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulator;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.pki.keystore.KeyStoreWrapper;
import org.hyperledger.besu.util.Subscribers;
@@ -266,18 +269,31 @@ public class QbftBesuControllerBuilder extends BftBesuControllerBuilder {
@Override
protected BftContext createConsensusContext(
final Blockchain blockchain, final WorldStateArchive worldStateArchive) {
final Blockchain blockchain,
final WorldStateArchive worldStateArchive,
final ProtocolSchedule protocolSchedule) {
final GenesisConfigOptions configOptions =
genesisConfig.getConfigOptions(genesisConfigOverrides);
final BftConfigOptions bftConfig = configOptions.getBftConfigOptions();
final EpochManager epochManager = new EpochManager(bftConfig.getEpochLength());
final QbftConfigOptions qbftConfig = configOptions.getQbftConfigOptions();
final EpochManager epochManager = new EpochManager(qbftConfig.getEpochLength());
final Map<Long, List<Address>> bftValidatorForkMap =
final BftValidatorOverrides validatorOverrides =
convertBftForks(configOptions.getTransitions().getQbftForks());
final ValidatorProvider validatorProvider =
BlockValidatorProvider.forkingValidatorProvider(
blockchain, epochManager, bftBlockInterface().get(), bftValidatorForkMap);
final ValidatorProvider validatorProvider;
if (qbftConfig.getValidatorContractAddress().isEmpty()) {
validatorProvider =
BlockValidatorProvider.forkingValidatorProvider(
blockchain, epochManager, bftBlockInterface().get(), validatorOverrides);
} else {
final Address contractAddress =
Address.fromHexString(qbftConfig.getValidatorContractAddress().get());
final TransactionSimulator transactionSimulator =
new TransactionSimulator(blockchain, worldStateArchive, protocolSchedule);
final ValidatorContractController validatorContractController =
new ValidatorContractController(contractAddress, transactionSimulator);
validatorProvider = new TransactionValidatorProvider(blockchain, validatorContractController);
}
if (pkiKeyStore.isPresent()) {
return new PkiQbftContext(
@@ -287,7 +303,7 @@ public class QbftBesuControllerBuilder extends BftBesuControllerBuilder {
}
}
private Map<Long, List<Address>> convertBftForks(final List<BftFork> bftForks) {
private BftValidatorOverrides convertBftForks(final List<BftFork> bftForks) {
final Map<Long, List<Address>> result = new HashMap<>();
for (final BftFork fork : bftForks) {
@@ -301,7 +317,7 @@ public class QbftBesuControllerBuilder extends BftBesuControllerBuilder {
.collect(Collectors.toList())));
}
return result;
return new BftValidatorOverrides(result);
}
private static MinedBlockObserver blockLogger(

View File

@@ -24,9 +24,6 @@ import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class BlockValidatorProvider implements ValidatorProvider {
@@ -39,32 +36,34 @@ public class BlockValidatorProvider implements ValidatorProvider {
final Blockchain blockchain,
final EpochManager epochManager,
final BlockInterface blockInterface,
final Map<Long, List<Address>> validatorForkMap) {
return new BlockValidatorProvider(blockchain, epochManager, blockInterface, validatorForkMap);
final BftValidatorOverrides bftValidatorOverrides) {
return new BlockValidatorProvider(
blockchain, epochManager, blockInterface, Optional.of(bftValidatorOverrides));
}
public static ValidatorProvider nonForkingValidatorProvider(
final Blockchain blockchain,
final EpochManager epochManager,
final BlockInterface blockInterface) {
return new BlockValidatorProvider(
blockchain, epochManager, blockInterface, Collections.emptyMap());
return new BlockValidatorProvider(blockchain, epochManager, blockInterface, Optional.empty());
}
private BlockValidatorProvider(
final Blockchain blockchain,
final EpochManager epochManager,
final BlockInterface blockInterface,
final Map<Long, List<Address>> validatorForkMap) {
final Optional<BftValidatorOverrides> bftValidatorOverrides) {
final VoteTallyUpdater voteTallyUpdater = new VoteTallyUpdater(epochManager, blockInterface);
final VoteProposer voteProposer = new VoteProposer();
this.voteTallyCache =
new ForkingVoteTallyCache(
blockchain,
voteTallyUpdater,
epochManager,
blockInterface,
new BftValidatorOverrides(validatorForkMap));
bftValidatorOverrides.isPresent()
? new ForkingVoteTallyCache(
blockchain,
voteTallyUpdater,
epochManager,
blockInterface,
bftValidatorOverrides.get())
: new VoteTallyCache(blockchain, voteTallyUpdater, epochManager, blockInterface);
this.voteProvider = new BlockVoteProvider(voteTallyCache, voteProposer);
this.blockInterface = blockInterface;
}

View File

@@ -72,6 +72,7 @@ dependencies {
implementation 'io.vertx:vertx-core'
implementation 'org.apache.tuweni:tuweni-bytes'
implementation 'org.apache.tuweni:tuweni-units'
implementation 'org.web3j:abi:5.0.0'
integrationTestImplementation project(path: ':config', configuration: 'testSupportArtifacts')
integrationTestImplementation project(path: ':ethereum:core', configuration: 'testSupportArtifacts')
@@ -93,6 +94,7 @@ dependencies {
integrationTestImplementation project(path: ':consensus:common', configuration: 'testSupportArtifacts')
integrationTestImplementation project(':metrics:core')
integrationTestImplementation project(':testutil')
integrationTestImplementation project(':crypto')
integrationTestImplementation 'junit:junit'
integrationTestImplementation 'org.assertj:assertj-core'

View File

@@ -22,6 +22,7 @@ import org.hyperledger.besu.consensus.common.bft.EventMultiplexer;
import org.hyperledger.besu.consensus.common.bft.inttest.NodeParams;
import org.hyperledger.besu.consensus.common.bft.statemachine.BftEventHandler;
import org.hyperledger.besu.consensus.common.bft.statemachine.BftFinalState;
import org.hyperledger.besu.consensus.common.validator.ValidatorProvider;
import org.hyperledger.besu.consensus.qbft.payload.MessageFactory;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.core.Address;
@@ -50,6 +51,7 @@ public class TestContext {
private final BftFinalState finalState;
private final EventMultiplexer eventMultiplexer;
private final MessageFactory messageFactory;
private final ValidatorProvider validatorProvider;
private final BftExtraDataCodec bftExtraDataCodec;
public TestContext(
@@ -60,6 +62,7 @@ public class TestContext {
final BftFinalState finalState,
final EventMultiplexer eventMultiplexer,
final MessageFactory messageFactory,
final ValidatorProvider validatorProvider,
final BftExtraDataCodec bftExtraDataCodec) {
this.remotePeers = remotePeers;
this.blockchain = blockchain;
@@ -68,6 +71,7 @@ public class TestContext {
this.finalState = finalState;
this.eventMultiplexer = eventMultiplexer;
this.messageFactory = messageFactory;
this.validatorProvider = validatorProvider;
this.bftExtraDataCodec = bftExtraDataCodec;
}
@@ -133,4 +137,8 @@ public class TestContext {
public long getCurrentChainHeight() {
return blockchain.getChainHeadBlockNumber();
}
public ValidatorProvider getValidatorProvider() {
return validatorProvider;
}
}

View File

@@ -57,8 +57,11 @@ import org.hyperledger.besu.consensus.qbft.statemachine.QbftBlockHeightManagerFa
import org.hyperledger.besu.consensus.qbft.statemachine.QbftController;
import org.hyperledger.besu.consensus.qbft.statemachine.QbftRoundFactory;
import org.hyperledger.besu.consensus.qbft.validation.MessageValidatorFactory;
import org.hyperledger.besu.consensus.qbft.validator.TransactionValidatorProvider;
import org.hyperledger.besu.consensus.qbft.validator.ValidatorContractController;
import org.hyperledger.besu.crypto.NodeKey;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.chain.GenesisState;
import org.hyperledger.besu.ethereum.chain.MinedBlockObserver;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.core.Address;
@@ -70,17 +73,23 @@ import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import org.hyperledger.besu.ethereum.core.Difficulty;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.core.MiningParameters;
import org.hyperledger.besu.ethereum.core.ProtocolScheduleFixture;
import org.hyperledger.besu.ethereum.core.Util;
import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulator;
import org.hyperledger.besu.ethereum.worldstate.DefaultWorldStateArchive;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import org.hyperledger.besu.plugin.services.MetricsSystem;
import org.hyperledger.besu.testutil.TestClock;
import org.hyperledger.besu.util.Subscribers;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;
@@ -91,6 +100,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;
import com.google.common.collect.Iterables;
@@ -99,6 +109,7 @@ import org.apache.tuweni.bytes.Bytes;
public class TestContextBuilder {
private static final MetricsSystem metricsSystem = new NoOpMetricsSystem();
private boolean useValidatorContract;
private static class ControllerAndState {
@@ -107,18 +118,21 @@ public class TestContextBuilder {
private final BftFinalState finalState;
private final EventMultiplexer eventMultiplexer;
private final MessageFactory messageFactory;
private final ValidatorProvider validatorProvider;
public ControllerAndState(
final BftExecutors bftExecutors,
final BftEventHandler eventHandler,
final BftFinalState finalState,
final EventMultiplexer eventMultiplexer,
final MessageFactory messageFactory) {
final MessageFactory messageFactory,
final ValidatorProvider validatorProvider) {
this.bftExecutors = bftExecutors;
this.eventHandler = eventHandler;
this.finalState = finalState;
this.eventMultiplexer = eventMultiplexer;
this.messageFactory = messageFactory;
this.validatorProvider = validatorProvider;
}
public BftExecutors getBftExecutors() {
@@ -140,6 +154,10 @@ public class TestContextBuilder {
public MessageFactory getMessageFactory() {
return messageFactory;
}
public ValidatorProvider getValidatorProvider() {
return validatorProvider;
}
}
public static final int EPOCH_LENGTH = 10_000;
@@ -150,6 +168,8 @@ public class TestContextBuilder {
public static final int DUPLICATE_MESSAGE_LIMIT = 100;
public static final int FUTURE_MESSAGES_MAX_DISTANCE = 10;
public static final int FUTURE_MESSAGES_LIMIT = 1000;
public static final Address VALIDATOR_CONTRACT_ADDRESS =
Address.fromHexString("0x0000000000000000000000000000000000008888");
private static final BftExtraDataCodec BFT_EXTRA_DATA_ENCODER = new QbftExtraDataCodec();
private Clock clock = Clock.fixed(Instant.MIN, ZoneId.of("UTC"));
@@ -157,6 +177,8 @@ public class TestContextBuilder {
private int validatorCount = 4;
private int indexOfFirstLocallyProposedBlock = 0; // Meaning first block is from remote peer.
private boolean useGossip = false;
private Optional<String> genesisFile = Optional.empty();
private List<NodeParams> nodeParams = Collections.emptyList();
public TestContextBuilder clock(final Clock clock) {
this.clock = clock;
@@ -179,19 +201,61 @@ public class TestContextBuilder {
return this;
}
public TestContextBuilder nodeParams(final List<NodeParams> nodeParams) {
this.nodeParams = nodeParams;
return this;
}
public TestContextBuilder useGossip(final boolean useGossip) {
this.useGossip = useGossip;
return this;
}
public TestContext build() {
final NetworkLayout networkNodes =
NetworkLayout.createNetworkLayout(validatorCount, indexOfFirstLocallyProposedBlock);
public TestContextBuilder genesisFile(final String genesisFile) {
this.genesisFile = Optional.of(genesisFile);
return this;
}
final Block genesisBlock = createGenesisBlock(networkNodes.getValidatorAddresses());
final MutableBlockchain blockChain =
createInMemoryBlockchain(
genesisBlock, BftBlockHeaderFunctions.forOnChainBlock(BFT_EXTRA_DATA_ENCODER));
public TestContextBuilder useValidatorContract(final boolean useValidatorContract) {
this.useValidatorContract = useValidatorContract;
return this;
}
public TestContext build() {
final NetworkLayout networkNodes;
if (nodeParams.isEmpty()) {
networkNodes =
NetworkLayout.createNetworkLayout(validatorCount, indexOfFirstLocallyProposedBlock);
} else {
final TreeMap<Address, NodeParams> addressKeyMap = new TreeMap<>();
for (NodeParams params : nodeParams) {
addressKeyMap.put(params.getAddress(), params);
}
final NodeParams localNode =
Iterables.get(addressKeyMap.values(), indexOfFirstLocallyProposedBlock);
networkNodes = new NetworkLayout(localNode, addressKeyMap);
}
final MutableBlockchain blockChain;
final DefaultWorldStateArchive worldStateArchive = createInMemoryWorldStateArchive();
if (genesisFile.isPresent()) {
try {
final GenesisState genesisState = createGenesisBlock(genesisFile.get());
blockChain =
createInMemoryBlockchain(
genesisState.getBlock(),
BftBlockHeaderFunctions.forOnChainBlock(BFT_EXTRA_DATA_ENCODER));
genesisState.writeStateTo(worldStateArchive.getMutable());
} catch (IOException e) {
throw new IllegalStateException(e);
}
} else {
final Block genesisBlock = createGenesisBlock(networkNodes.getValidatorAddresses());
blockChain =
createInMemoryBlockchain(
genesisBlock, BftBlockHeaderFunctions.forOnChainBlock(BFT_EXTRA_DATA_ENCODER));
}
// Use a stubbed version of the multicaster, to prevent creating PeerConnections etc.
final StubValidatorMulticaster multicaster = new StubValidatorMulticaster();
@@ -205,12 +269,14 @@ public class TestContextBuilder {
final ControllerAndState controllerAndState =
createControllerAndFinalState(
blockChain,
worldStateArchive,
multicaster,
networkNodes.getLocalNode().getNodeKey(),
clock,
bftEventQueue,
gossiper,
synchronizerUpdater);
synchronizerUpdater,
useValidatorContract);
// Add each networkNode to the Multicaster (such that each can receive msgs from local node).
// NOTE: the remotePeers needs to be ordered based on Address (as this is used to determine
@@ -242,6 +308,7 @@ public class TestContextBuilder {
controllerAndState.getFinalState(),
controllerAndState.getEventMultiplexer(),
controllerAndState.getMessageFactory(),
controllerAndState.getValidatorProvider(),
BFT_EXTRA_DATA_ENCODER);
}
@@ -272,16 +339,21 @@ public class TestContextBuilder {
genesisHeader, new BlockBody(Collections.emptyList(), Collections.emptyList()));
}
private GenesisState createGenesisBlock(final String genesisFile) throws IOException {
final String json = Files.readString(Path.of(genesisFile));
return GenesisState.fromJson(json, ProtocolScheduleFixture.MAINNET);
}
private static ControllerAndState createControllerAndFinalState(
final MutableBlockchain blockChain,
final WorldStateArchive worldStateArchive,
final StubValidatorMulticaster multicaster,
final NodeKey nodeKey,
final Clock clock,
final BftEventQueue bftEventQueue,
final Gossiper gossiper,
final SynchronizerUpdater synchronizerUpdater) {
final WorldStateArchive worldStateArchive = createInMemoryWorldStateArchive();
final SynchronizerUpdater synchronizerUpdater,
final boolean useValidatorContract) {
final MiningParameters miningParams =
new MiningParameters.Builder()
@@ -306,9 +378,18 @@ public class TestContextBuilder {
final BftBlockInterface blockInterface = new BftBlockInterface(BFT_EXTRA_DATA_ENCODER);
final ValidatorProvider validatorProvider =
BlockValidatorProvider.nonForkingValidatorProvider(
blockChain, epochManager, blockInterface);
final ValidatorProvider validatorProvider;
if (useValidatorContract) {
final TransactionSimulator transactionSimulator =
new TransactionSimulator(blockChain, worldStateArchive, protocolSchedule);
final ValidatorContractController validatorContractController =
new ValidatorContractController(VALIDATOR_CONTRACT_ADDRESS, transactionSimulator);
validatorProvider = new TransactionValidatorProvider(blockChain, validatorContractController);
} else {
validatorProvider =
BlockValidatorProvider.nonForkingValidatorProvider(
blockChain, epochManager, blockInterface);
}
final ProtocolContext protocolContext =
new ProtocolContext(
@@ -394,6 +475,11 @@ public class TestContextBuilder {
//////////////////////////// END IBFT BesuController ////////////////////////////
return new ControllerAndState(
bftExecutors, qbftController, finalState, eventMultiplexer, messageFactory);
bftExecutors,
qbftController,
finalState,
eventMultiplexer,
messageFactory,
validatorProvider);
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 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.
*
* 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.consensus.common.bft.ConsensusRoundIdentifier;
import org.hyperledger.besu.consensus.common.bft.events.BlockTimerExpiry;
import org.hyperledger.besu.consensus.common.bft.inttest.NodeParams;
import org.hyperledger.besu.consensus.common.validator.ValidatorProvider;
import org.hyperledger.besu.consensus.qbft.support.TestContext;
import org.hyperledger.besu.consensus.qbft.support.TestContextBuilder;
import org.hyperledger.besu.crypto.NodeKeyUtils;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;
import java.util.List;
import com.google.common.io.Resources;
import org.apache.tuweni.bytes.Bytes32;
import org.junit.Test;
public class ValidatorContractTest {
public static final Address NODE_ADDRESS =
Address.fromHexString("0xeac51e3fe1afc9894f0dfeab8ceb471899b932df");
public static final Bytes32 NODE_PRIVATE_KEY =
Bytes32.fromHexString("0xa3bdf521b0f286a80918c4b67000dfd2a2bdef97e94d268016ef9ec86648eac3");
private final long blockTimeStamp = 100;
private final Clock fixedClock =
Clock.fixed(Instant.ofEpochSecond(blockTimeStamp), ZoneId.systemDefault());
// Configuration ensures unit under test will be responsible for sending first proposal
@SuppressWarnings("UnstableApiUsage")
private final TestContext context =
new TestContextBuilder()
.indexOfFirstLocallyProposedBlock(0)
.nodeParams(
List.of(new NodeParams(NODE_ADDRESS, NodeKeyUtils.createFrom(NODE_PRIVATE_KEY))))
.clock(fixedClock)
.genesisFile(Resources.getResource("genesis_validator_contract.json").getFile())
.useValidatorContract(true)
.buildAndStart();
private final ConsensusRoundIdentifier roundId = new ConsensusRoundIdentifier(1, 0);
@Test
public void retrievesValidatorsFromValidatorContract() {
// create new block
context.getController().handleBlockTimerExpiry(new BlockTimerExpiry(roundId));
assertThat(context.getBlockchain().getChainHeadBlockNumber()).isEqualTo(1);
final ValidatorProvider validatorProvider = context.getValidatorProvider();
final BlockHeader genesisBlock = context.getBlockchain().getBlockHeader(0).get();
final BlockHeader block1 = context.getBlockchain().getBlockHeader(1).get();
assertThat(validatorProvider.getValidatorsForBlock(genesisBlock)).containsExactly(NODE_ADDRESS);
assertThat(validatorProvider.getValidatorsForBlock(block1)).containsExactly(NODE_ADDRESS);
}
}

View File

@@ -0,0 +1,43 @@
{
"config": {
"chainid": 2017,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0
},
"nonce": "0x0",
"timestamp": "0x0",
"extraData": "0xe5a00000000000000000000000000000000000000000000000000000000000000000c0c080c0",
"gasLimit": "0x29b92700",
"difficulty": "0x1",
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
"coinbase": "0x0000000000000000000000000000000000000000",
"alloc": {
"64d9be4177f418bcf4e56adad85f33e3a64efe22": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"
},
"9f66f8a0f0a6537e4a36aa1799673ea7ae97a166": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"
},
"a7f25969fb6f3d5ac09a88862c90b5ff664557a7": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"
},
"f4bbfd32c11c9d63e9b4c77bb225810f840342df": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"
},
"0x0000000000000000000000000000000000008888": {
"comment": "validator smart contract. This is compiled from validator_contract.sol using solc --evm-version byzantium --bin-runtime validator_contract.sol",
"balance": "0",
"code": "608060405234801561001057600080fd5b5060043610610048576000357c010000000000000000000000000000000000000000000000000000000090048063b7ab4db51461004d575b600080fd5b61005561006b565b604051610062919061017e565b60405180910390f35b606060008054806020026020016040519081016040528092919081815260200182805480156100ef57602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190600101908083116100a5575b5050505050905090565b60006101058383610111565b60208301905092915050565b61011a816101d9565b82525050565b600061012b826101b0565b61013581856101c8565b9350610140836101a0565b8060005b8381101561017157815161015888826100f9565b9750610163836101bb565b925050600181019050610144565b5085935050505092915050565b600060208201905081810360008301526101988184610120565b905092915050565b6000819050602082019050919050565b600081519050919050565b6000602082019050919050565b600082825260208201905092915050565b60006101e4826101eb565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff8216905091905056fea26469706673582212206d880cf012c1677c691bf6f2f0a0e4eadf57866ffe5cd2d9833d3cfdf27b15f664736f6c63430008060033",
"storage": {
"0000000000000000000000000000000000000000000000000000000000000000": "0000000000000000000000000000000000000000000000000000000000000001",
"290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563": "000000000000000000000000eac51e3fe1afc9894f0dfeab8ceb471899b932df"
}
}
},
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
pragma solidity >= 0.5.3;
contract Validators {
address[] validators;
constructor() {}
function getValidators() public view returns (address[] memory) {
return validators;
}
}

View File

@@ -0,0 +1,47 @@
package org.hyperledger.besu.consensus.qbft.validator;
/*
* Copyright 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
import org.hyperledger.besu.consensus.common.validator.ValidatorVote;
import org.hyperledger.besu.consensus.common.validator.VoteProvider;
import org.hyperledger.besu.consensus.common.validator.VoteType;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
public class NoOpTransactionVoteProvider implements VoteProvider {
@Override
public Optional<ValidatorVote> getVoteAfterBlock(
final BlockHeader header, final Address localAddress) {
return Optional.empty();
}
@Override
public void authVote(final Address address) {}
@Override
public void dropVote(final Address address) {}
@Override
public void discardVote(final Address address) {}
@Override
public Map<Address, VoteType> getProposals() {
return Collections.emptyMap();
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.consensus.qbft.validator;
import org.hyperledger.besu.consensus.common.validator.ValidatorProvider;
import org.hyperledger.besu.consensus.common.validator.VoteProvider;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import java.util.Collection;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
public class TransactionValidatorProvider implements ValidatorProvider {
private final Blockchain blockchain;
private final ValidatorContractController validatorContractController;
private final Cache<Long, Collection<Address>> validatorCache =
CacheBuilder.newBuilder().maximumSize(100).build();
public TransactionValidatorProvider(
final Blockchain blockchain, final ValidatorContractController validatorContractController) {
this.blockchain = blockchain;
this.validatorContractController = validatorContractController;
}
@Override
public Collection<Address> getValidatorsAtHead() {
return getValidatorsForBlock(blockchain.getChainHeadHeader());
}
@Override
public Collection<Address> getValidatorsAfterBlock(final BlockHeader header) {
// For the validator contract we determine the vote from the previous block
return getValidatorsForBlock(header);
}
@Override
public Collection<Address> getValidatorsForBlock(final BlockHeader header) {
final long blockNumber = header.getNumber();
try {
return validatorCache.get(
blockNumber,
() ->
validatorContractController.getValidators(blockNumber).stream()
.sorted()
.collect(Collectors.toList()));
} catch (final ExecutionException e) {
throw new RuntimeException("Unable to determine a validators for the requested block.");
}
}
@Override
public Optional<VoteProvider> getVoteProvider() {
return Optional.of(new NoOpTransactionVoteProvider());
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.consensus.qbft.validator;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.transaction.CallParameter;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulator;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulatorResult;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.tuweni.bytes.Bytes;
import org.web3j.abi.DefaultFunctionReturnDecoder;
import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.DynamicArray;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.Type;
public class ValidatorContractController {
public static final String GET_VALIDATORS = "getValidators";
public static final String CONTRACT_ERROR_MSG = "Failed validator smart contract call";
private final Address contractAddress;
private final TransactionSimulator transactionSimulator;
private final Function getValidatorsFunction;
public ValidatorContractController(
final Address contractAddress, final TransactionSimulator transactionSimulator) {
this.contractAddress = contractAddress;
this.transactionSimulator = transactionSimulator;
try {
this.getValidatorsFunction =
new Function(
GET_VALIDATORS,
List.of(),
List.of(new TypeReference<DynamicArray<org.web3j.abi.datatypes.Address>>() {}));
} catch (final Exception e) {
throw new RuntimeException("Error creating smart contract function", e);
}
}
public Collection<Address> getValidators(final long blockNumber) {
return callFunction(blockNumber, getValidatorsFunction)
.map(this::parseGetValidatorsResult)
.orElseThrow(() -> new IllegalStateException(CONTRACT_ERROR_MSG));
}
@SuppressWarnings({"rawtypes", "unchecked"})
private Collection<Address> parseGetValidatorsResult(final TransactionSimulatorResult result) {
final List<Type> resultDecoding = decodeResult(result, getValidatorsFunction);
final List<org.web3j.abi.datatypes.Address> addresses =
(List<org.web3j.abi.datatypes.Address>) resultDecoding.get(0).getValue();
return addresses.stream()
.map(a -> Address.fromHexString(a.getValue()))
.collect(Collectors.toList());
}
private Optional<TransactionSimulatorResult> callFunction(
final long blockNumber, final Function function) {
final Bytes payload = Bytes.fromHexString(FunctionEncoder.encode(function));
final CallParameter callParams =
new CallParameter(null, contractAddress, -1, null, null, payload);
return transactionSimulator.process(callParams, blockNumber);
}
@SuppressWarnings("rawtypes")
private List<Type> decodeResult(
final TransactionSimulatorResult result, final Function function) {
if (result.isSuccessful()) {
return DefaultFunctionReturnDecoder.decode(
result.getResult().getOutput().toHexString(), function.getOutputParameters());
} else {
throw new IllegalStateException("Failed validator smart contract call");
}
}
}

View File

@@ -0,0 +1,175 @@
/*
* Copyright 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.consensus.qbft.validator;
import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryBlockchain;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.AddressHelpers;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockBody;
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import org.hyperledger.besu.ethereum.core.Hash;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import com.google.common.collect.Lists;
import org.apache.tuweni.bytes.Bytes;
import org.junit.Before;
import org.junit.Test;
public class TransactionValidatorProviderTest {
private final ValidatorContractController validatorContractController =
mock(ValidatorContractController.class);
protected MutableBlockchain blockChain;
protected Block genesisBlock;
protected Block block_1;
protected Block block_2;
private Block block_3;
private final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture();
@Before
public void setup() {
genesisBlock = createEmptyBlock(0, Hash.ZERO);
blockChain = createInMemoryBlockchain(genesisBlock);
headerBuilder.extraData(Bytes.wrap(new byte[32]));
block_1 = createEmptyBlock(1, genesisBlock.getHeader().getHash());
block_2 = createEmptyBlock(2, block_1.getHeader().getHash());
block_3 = createEmptyBlock(3, block_2.getHeader().getHash());
blockChain.appendBlock(block_1, emptyList());
blockChain.appendBlock(block_2, emptyList());
blockChain.appendBlock(block_3, emptyList());
}
private Block createEmptyBlock(final long blockNumber, final Hash parentHash) {
headerBuilder.number(blockNumber).parentHash(parentHash).coinbase(AddressHelpers.ofValue(0));
return new Block(headerBuilder.buildHeader(), new BlockBody(emptyList(), emptyList()));
}
@Test
public void validatorsAfterBlockAreRetrievedUsingContractController() {
final List<Address> validatorsAt2 =
Lists.newArrayList(Address.fromHexString("5"), Address.fromHexString("6"));
final List<Address> validatorsAt3 =
Lists.newArrayList(
Address.fromHexString("5"), Address.fromHexString("6"), Address.fromHexString("7"));
when(validatorContractController.getValidators(2)).thenReturn(validatorsAt2);
when(validatorContractController.getValidators(3)).thenReturn(validatorsAt3);
final TransactionValidatorProvider validatorProvider =
new TransactionValidatorProvider(blockChain, validatorContractController);
assertThat(validatorProvider.getValidatorsAfterBlock(block_2.getHeader()))
.containsExactlyElementsOf(validatorsAt2);
assertThat(validatorProvider.getValidatorsAfterBlock(block_3.getHeader()))
.containsExactlyElementsOf(validatorProvider.getValidatorsAfterBlock(block_3.getHeader()));
}
@Test
public void validatorsForBlockAreRetrievedUsingContractController() {
final List<Address> validatorsAt2 =
Lists.newArrayList(Address.fromHexString("5"), Address.fromHexString("6"));
final List<Address> validatorsAt3 =
Lists.newArrayList(
Address.fromHexString("5"), Address.fromHexString("6"), Address.fromHexString("7"));
when(validatorContractController.getValidators(2)).thenReturn(validatorsAt2);
when(validatorContractController.getValidators(3)).thenReturn(validatorsAt3);
final TransactionValidatorProvider validatorProvider =
new TransactionValidatorProvider(blockChain, validatorContractController);
assertThat(validatorProvider.getValidatorsForBlock(block_2.getHeader()))
.containsExactlyElementsOf(validatorsAt2);
assertThat(validatorProvider.getValidatorsForBlock(block_3.getHeader()))
.containsExactlyElementsOf(validatorProvider.getValidatorsForBlock(block_3.getHeader()));
}
@Test
public void validatorsAtHeadAreRetrievedUsingContractController() {
final List<Address> validators =
Lists.newArrayList(Address.fromHexString("5"), Address.fromHexString("6"));
when(validatorContractController.getValidators(3)).thenReturn(validators);
final TransactionValidatorProvider validatorProvider =
new TransactionValidatorProvider(blockChain, validatorContractController);
assertThat(validatorProvider.getValidatorsAtHead()).containsExactlyElementsOf(validators);
}
@Test
public void validatorsAtHeadContractCallIsCached() {
final List<Address> validators =
Lists.newArrayList(Address.fromHexString("5"), Address.fromHexString("6"));
when(validatorContractController.getValidators(3)).thenReturn(validators);
final TransactionValidatorProvider validatorProvider =
new TransactionValidatorProvider(blockChain, validatorContractController);
assertThat(validatorProvider.getValidatorsAtHead()).containsExactlyElementsOf(validators);
verify(validatorContractController).getValidators(3);
assertThat(validatorProvider.getValidatorsAtHead()).containsExactlyElementsOf(validators);
verifyNoMoreInteractions(validatorContractController);
}
@Test
public void validatorsAfterBlockContractCallIsCached() {
final List<Address> validators =
Lists.newArrayList(Address.fromHexString("5"), Address.fromHexString("6"));
when(validatorContractController.getValidators(2)).thenReturn(validators);
final TransactionValidatorProvider validatorProvider =
new TransactionValidatorProvider(blockChain, validatorContractController);
final Collection<Address> result =
validatorProvider.getValidatorsAfterBlock(block_2.getHeader());
assertThat(result).containsExactlyElementsOf(validators);
verify(validatorContractController).getValidators(2);
final Collection<Address> resultCached =
validatorProvider.getValidatorsAfterBlock(block_2.getHeader());
assertThat(resultCached).containsExactlyElementsOf(validators);
verifyNoMoreInteractions(validatorContractController);
}
@Test
public void validatorsMustBeSorted() {
final List<Address> validators =
Lists.newArrayList(
Address.fromHexString("9"), Address.fromHexString("8"), Address.fromHexString("7"));
when(validatorContractController.getValidators(3)).thenReturn(validators);
final TransactionValidatorProvider validatorProvider =
new TransactionValidatorProvider(blockChain, validatorContractController);
final Collection<Address> result = validatorProvider.getValidatorsAtHead();
final List<Address> expectedValidators =
validators.stream().sorted().collect(Collectors.toList());
assertThat(result).containsExactlyElementsOf(expectedValidators);
}
}

View File

@@ -0,0 +1,131 @@
/*
* Copyright 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.consensus.qbft.validator;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.consensus.qbft.validator.ValidatorContractController.GET_VALIDATORS;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
import org.hyperledger.besu.ethereum.transaction.CallParameter;
import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulator;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulatorResult;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.assertj.core.api.Assertions;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.DynamicArray;
import org.web3j.abi.datatypes.Function;
public class ValidatorContractControllerTest {
public static final String GET_VALIDATORS_FUNCTION_RESULT =
"00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000eac51e3fe1afc9894f0dfeab8ceb471899b932df";
public static final Address VALIDATOR_ADDRESS =
Address.fromHexString("0xeac51e3fe1afc9894f0dfeab8ceb471899b932df");
final Address CONTRACT_ADDRESS = Address.fromHexString("1");
private final TransactionSimulator transactionSimulator =
Mockito.mock(TransactionSimulator.class);
private final Transaction transaction = Mockito.mock(Transaction.class);
private CallParameter callParameter;
@Before
public void setup() {
final Function getValidatorsFunction =
new Function(
GET_VALIDATORS,
List.of(),
List.of(new TypeReference<DynamicArray<org.web3j.abi.datatypes.Address>>() {}));
final Bytes payload = Bytes.fromHexString(FunctionEncoder.encode(getValidatorsFunction));
callParameter = new CallParameter(null, CONTRACT_ADDRESS, -1, null, null, payload);
}
@Test
public void decodesGetValidatorsResultFromContractCall() {
final TransactionSimulatorResult result =
new TransactionSimulatorResult(
transaction,
TransactionProcessingResult.successful(
List.of(),
0,
0,
Bytes.fromHexString(GET_VALIDATORS_FUNCTION_RESULT),
ValidationResult.valid()));
when(transactionSimulator.process(callParameter, 1)).thenReturn(Optional.of(result));
final ValidatorContractController validatorContractController =
new ValidatorContractController(CONTRACT_ADDRESS, transactionSimulator);
final Collection<Address> validators = validatorContractController.getValidators(1);
assertThat(validators).containsExactly(VALIDATOR_ADDRESS);
}
@Test
public void throwErrorIfInvalidSimulationResult() {
final TransactionSimulatorResult result =
new TransactionSimulatorResult(
transaction,
TransactionProcessingResult.invalid(
ValidationResult.invalid(TransactionInvalidReason.INTERNAL_ERROR)));
when(transactionSimulator.process(callParameter, 1)).thenReturn(Optional.of(result));
final ValidatorContractController validatorContractController =
new ValidatorContractController(CONTRACT_ADDRESS, transactionSimulator);
Assertions.assertThatThrownBy(() -> validatorContractController.getValidators(1))
.hasMessage("Failed validator smart contract call");
}
@Test
public void throwErrorIfFailedSimulationResult() {
final TransactionSimulatorResult result =
new TransactionSimulatorResult(
transaction,
TransactionProcessingResult.failed(
0,
0,
ValidationResult.invalid(TransactionInvalidReason.INTERNAL_ERROR),
Optional.empty()));
when(transactionSimulator.process(callParameter, 1)).thenReturn(Optional.of(result));
final ValidatorContractController validatorContractController =
new ValidatorContractController(CONTRACT_ADDRESS, transactionSimulator);
Assertions.assertThatThrownBy(() -> validatorContractController.getValidators(1))
.hasMessage("Failed validator smart contract call");
}
@Test
public void throwErrorIfEmptySimulationResult() {
when(transactionSimulator.process(callParameter, 1)).thenReturn(Optional.empty());
final ValidatorContractController validatorContractController =
new ValidatorContractController(CONTRACT_ADDRESS, transactionSimulator);
Assertions.assertThatThrownBy(() -> validatorContractController.getValidators(1))
.hasMessage("Failed validator smart contract call");
}
}

View File

@@ -14,12 +14,21 @@
*/
package org.hyperledger.besu.crypto;
import org.apache.tuweni.bytes.Bytes32;
public class NodeKeyUtils {
public static NodeKey createFrom(final KeyPair keyPair) {
return new NodeKey(new KeyPairSecurityModule(keyPair));
}
public static NodeKey createFrom(final Bytes32 privateKey) {
final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithmFactory.getInstance();
final KeyPair keyPair =
signatureAlgorithm.createKeyPair(signatureAlgorithm.createPrivateKey(privateKey));
return new NodeKey(new KeyPairSecurityModule(keyPair));
}
public static NodeKey generate() {
return new NodeKey(
new KeyPairSecurityModule(SignatureAlgorithmFactory.getInstance().generateKeyPair()));

View File

@@ -0,0 +1,28 @@
/*
* Copyright 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
@FunctionalInterface
public interface ConsensusContextFactory {
Object create(
Blockchain blockchain,
WorldStateArchive worldStateArchive,
ProtocolSchedule protocolSchedule);
}

View File

@@ -14,13 +14,11 @@
*/
package org.hyperledger.besu.ethereum;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.chain.GenesisState;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import java.util.function.BiFunction;
/**
* Holds the mutable state used to track the current context of the protocol. This is primarily the
* blockchain and world state archive, but can also hold arbitrary context required by a particular
@@ -44,7 +42,8 @@ public class ProtocolContext {
final MutableBlockchain blockchain,
final WorldStateArchive worldStateArchive,
final GenesisState genesisState,
final BiFunction<Blockchain, WorldStateArchive, Object> consensusContextFactory) {
final ProtocolSchedule protocolSchedule,
final ConsensusContextFactory consensusContextFactory) {
if (blockchain.getChainHeadBlockNumber() < 1) {
genesisState.writeStateTo(worldStateArchive.getMutable());
}
@@ -52,7 +51,7 @@ public class ProtocolContext {
return new ProtocolContext(
blockchain,
worldStateArchive,
consensusContextFactory.apply(blockchain, worldStateArchive));
consensusContextFactory.create(blockchain, worldStateArchive, protocolSchedule));
}
public MutableBlockchain getBlockchain() {