mirror of
https://github.com/vacp2p/linea-besu.git
synced 2026-01-09 15:37:54 -05:00
Qbft to use validator contract (#2574)
Signed-off-by: Jason Frame <jasonwframe@gmail.com>
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user