ibft mining acceptance tests (#483)

Jason Frame 6 years ago committed by GitHub
parent 5e46d1b2ba
commit ba3c2a500a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      acceptance-tests/build.gradle
  2. 9
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/AcceptanceTestBase.java
  3. 11
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Clique.java
  4. 30
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Ibft.java
  5. 4
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/GenesisConfigProvider.java
  6. 2
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNodeRunner.java
  7. 7
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ProcessPantheonNodeRunner.java
  8. 10
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ThreadPantheonNodeRunner.java
  9. 10
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/cluster/Cluster.java
  10. 116
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonNodeFactory.java
  11. 112
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/ibft/IbftMiningAcceptanceTest.java
  12. 2
      acceptance-tests/src/test/resources/clique/clique.json
  13. 43
      acceptance-tests/src/test/resources/ibft/ibft.json
  14. 12
      consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftExtraData.java

@ -18,6 +18,7 @@ dependencies {
testImplementation project(':config')
testImplementation project(':consensus:clique')
testImplementation project(':consensus:ibft')
testImplementation project(':crypto')
testImplementation project(':ethereum:eth')
testImplementation project(':ethereum:core')

@ -14,9 +14,10 @@ package tech.pegasys.pantheon.tests.acceptance.dsl;
import tech.pegasys.pantheon.tests.acceptance.dsl.account.Accounts;
import tech.pegasys.pantheon.tests.acceptance.dsl.blockchain.Blockchain;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.clique.CliqueConditions;
import tech.pegasys.pantheon.tests.acceptance.dsl.contract.ContractVerifier;
import tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc.Clique;
import tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc.Eth;
import tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc.Ibft;
import tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc.Net;
import tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc.Perm;
import tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc.Web3;
@ -36,9 +37,10 @@ public class AcceptanceTestBase {
protected final Accounts accounts;
protected final Blockchain blockchain;
protected final Cluster cluster;
protected final CliqueConditions clique;
protected final CliqueTransactions cliqueTransactions;
protected final Transactions transactions;
protected final Clique clique;
protected final Ibft ibft;
protected final Web3 web3;
protected final Eth eth;
protected final Net net;
@ -53,7 +55,8 @@ public class AcceptanceTestBase {
blockchain = new Blockchain(ethTransactions);
eth = new Eth(ethTransactions);
cliqueTransactions = new CliqueTransactions();
clique = new CliqueConditions(ethTransactions, cliqueTransactions);
clique = new Clique(ethTransactions, cliqueTransactions);
ibft = new Ibft();
net = new Net(new NetTransactions());
cluster = new Cluster(net);
transactions = new Transactions(accounts);

@ -10,7 +10,7 @@
* 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.
*/
package tech.pegasys.pantheon.tests.acceptance.dsl.condition.clique;
package tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc;
import static java.util.Collections.emptyList;
import static tech.pegasys.pantheon.ethereum.core.Hash.fromHexString;
@ -20,7 +20,12 @@ import tech.pegasys.pantheon.config.GenesisConfigFile;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.blockchain.ExpectBlockNotCreated;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.clique.ExpectNonceVote;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.clique.ExpectNonceVote.CLIQUE_NONCE_VOTE;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.clique.ExpectProposals;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.clique.ExpectValidators;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.clique.ExpectValidatorsAtBlock;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.clique.ExpectValidatorsAtBlockHash;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.Node;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNode;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.clique.CliqueTransactions;
@ -36,11 +41,11 @@ import java.util.stream.Collectors;
import com.google.common.collect.ImmutableMap;
import org.web3j.protocol.core.DefaultBlockParameter;
public class CliqueConditions {
public class Clique {
private final EthTransactions eth;
private final CliqueTransactions clique;
public CliqueConditions(final EthTransactions eth, final CliqueTransactions clique) {
public Clique(final EthTransactions eth, final CliqueTransactions clique) {
this.eth = eth;
this.clique = clique;
}

@ -0,0 +1,30 @@
/*
* Copyright 2019 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.
*/
package tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNode;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class Ibft {
public List<PantheonNode> validators(final PantheonNode[] nodes) {
final Comparator<PantheonNode> compareByAddress =
Comparator.comparing(PantheonNode::getAddress);
List<PantheonNode> pantheonNodes = Arrays.asList(nodes);
pantheonNodes.sort(compareByAddress);
return pantheonNodes;
}
}

@ -12,10 +12,10 @@
*/
package tech.pegasys.pantheon.tests.acceptance.dsl.node;
import java.util.List;
import java.util.Collection;
import java.util.Optional;
@FunctionalInterface
public interface GenesisConfigProvider {
Optional<String> createGenesisConfig(final List<RunnableNode> validators);
Optional<String> createGenesisConfig(final Collection<? extends RunnableNode> validators);
}

@ -36,6 +36,8 @@ public interface PantheonNodeRunner {
void shutdown();
boolean isActive(String nodeName);
default void waitForPortsFile(final Path dataDir) {
final File file = new File(dataDir.toFile(), "pantheon.ports");
Awaitility.waitAtMost(30, TimeUnit.SECONDS)

@ -150,6 +150,12 @@ public class ProcessPantheonNodeRunner implements PantheonNodeRunner {
localMap.forEach(this::killPantheonProcess);
}
@Override
public boolean isActive(final String nodeName) {
final Process process = pantheonProcesses.get(nodeName);
return process.isAlive();
}
private void killPantheonProcess(final String name, final Process process) {
LOG.info("Killing " + name + " process");
@ -158,6 +164,7 @@ public class ProcessPantheonNodeRunner implements PantheonNodeRunner {
() -> {
if (process.isAlive()) {
process.destroy();
pantheonProcesses.remove(name);
return false;
} else {
pantheonProcesses.remove(name);

@ -27,6 +27,7 @@ import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -105,7 +106,8 @@ public class ThreadPantheonNodeRunner implements PantheonNodeRunner {
@Override
public void shutdown() {
pantheonRunners.keySet().forEach(this::killRunner);
// iterate over a copy of the set so that pantheonRunner can be updated when a runner is killed
new HashSet<>(pantheonRunners.keySet()).forEach(this::killRunner);
try {
nodeExecutor.shutdownNow();
if (!nodeExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
@ -116,12 +118,18 @@ public class ThreadPantheonNodeRunner implements PantheonNodeRunner {
}
}
@Override
public boolean isActive(final String nodeName) {
return pantheonRunners.containsKey(nodeName);
}
private void killRunner(final String name) {
LOG.info("Killing " + name + " runner");
if (pantheonRunners.containsKey(name)) {
try {
pantheonRunners.get(name).close();
pantheonRunners.remove(name);
} catch (final Exception e) {
throw new RuntimeException("Error shutting down node " + name, e);
}

@ -59,7 +59,7 @@ public class Cluster implements AutoCloseable {
.collect(Collectors.toList()));
}
public void start(final List<RunnableNode> nodes) {
public void start(final List<? extends RunnableNode> nodes) {
this.nodes.clear();
final List<String> bootNodes = new ArrayList<>();
@ -112,6 +112,14 @@ public class Cluster implements AutoCloseable {
}
}
public void verifyOnActiveNodes(final Condition condition) {
nodes
.values()
.stream()
.filter(node -> pantheonNodeRunner.isActive(node.getName()))
.forEach(condition::verify);
}
public void waitUntil(final WaitCondition condition) {
for (final Node node : nodes.values()) {
node.waitUntil(condition);

@ -16,14 +16,17 @@ import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;
import static tech.pegasys.pantheon.consensus.clique.jsonrpc.CliqueRpcApis.CLIQUE;
import static tech.pegasys.pantheon.consensus.ibft.jsonrpc.IbftRpcApis.IBFT;
import tech.pegasys.pantheon.consensus.clique.CliqueExtraData;
import tech.pegasys.pantheon.consensus.ibft.IbftExtraData;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration;
import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApi;
import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApis;
import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration;
import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.GenesisConfigProvider;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNode;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.RunnableNode;
@ -36,6 +39,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import com.google.common.io.Resources;
@ -180,62 +184,108 @@ public class PantheonNodeFactory {
new PantheonFactoryConfigurationBuilder()
.setName(name)
.miningEnabled()
.setJsonRpcConfiguration(jsonRpcConfigWithClique())
.setJsonRpcConfiguration(createJsonRpcConfigWithClique())
.setWebSocketConfiguration(createWebSocketEnabledConfig())
.setDevMode(false)
.setGenesisConfigProvider(this::createCliqueGenesisConfig)
.build());
}
private Optional<String> createCliqueGenesisConfig(final Collection<RunnableNode> validators) {
String genesisTemplate = cliqueGenesisTemplateConfig();
String cliqueExtraData = encodeCliqueExtraData(validators);
String genesis = genesisTemplate.replaceAll("%cliqueExtraData%", cliqueExtraData);
return Optional.of(genesis);
public PantheonNode createIbftNode(final String name) throws IOException {
return create(
new PantheonFactoryConfigurationBuilder()
.setName(name)
.miningEnabled()
.setJsonRpcConfiguration(createJsonRpcConfigWithIbft())
.setWebSocketConfiguration(createWebSocketEnabledConfig())
.setDevMode(false)
.setGenesisConfigProvider(this::createIbftGenesisConfig)
.build());
}
private String encodeCliqueExtraData(final Collection<RunnableNode> nodes) {
final List<Address> addresses = nodes.stream().map(RunnableNode::getAddress).collect(toList());
return CliqueExtraData.createGenesisExtraDataString(addresses);
}
public PantheonNode createCliqueNodeWithValidators(final String name, final String... validators)
throws IOException {
private String cliqueGenesisTemplateConfig() {
try {
URI uri = Resources.getResource("clique/clique.json").toURI();
return Resources.toString(uri.toURL(), Charset.defaultCharset());
} catch (URISyntaxException | IOException e) {
throw new IllegalStateException("Unable to get test clique genesis config");
}
return create(
new PantheonFactoryConfigurationBuilder()
.setName(name)
.miningEnabled()
.setJsonRpcConfiguration(createJsonRpcConfigWithClique())
.setWebSocketConfiguration(createWebSocketEnabledConfig())
.setDevMode(false)
.setGenesisConfigProvider(
nodes ->
createGenesisConfigForValidators(
asList(validators), nodes, this::createCliqueGenesisConfig))
.build());
}
public PantheonNode createCliqueNodeWithValidators(final String name, final String... validators)
public PantheonNode createIbftNodeWithValidators(final String name, final String... validators)
throws IOException {
return create(
new PantheonFactoryConfigurationBuilder()
.setName(name)
.miningEnabled()
.setJsonRpcConfiguration(jsonRpcConfigWithClique())
.setJsonRpcConfiguration(createJsonRpcConfigWithIbft())
.setWebSocketConfiguration(createWebSocketEnabledConfig())
.setDevMode(false)
.setGenesisConfigProvider(
nodes -> createCliqueGenesisConfigForValidators(asList(validators), nodes))
nodes ->
createGenesisConfigForValidators(
asList(validators), nodes, this::createIbftGenesisConfig))
.build());
}
private Optional<String> createCliqueGenesisConfigForValidators(
final Collection<String> validators, final Collection<RunnableNode> pantheonNodes) {
List<RunnableNode> collect =
private Optional<String> createCliqueGenesisConfig(
final Collection<? extends RunnableNode> validators) {
final String template = genesisTemplateConfig("clique/clique.json");
return updateGenesisExtraData(
validators, template, CliqueExtraData::createGenesisExtraDataString);
}
private Optional<String> createIbftGenesisConfig(
final Collection<? extends RunnableNode> validators) {
final String template = genesisTemplateConfig("ibft/ibft.json");
return updateGenesisExtraData(
validators, template, IbftExtraData::createGenesisExtraDataString);
}
private Optional<String> updateGenesisExtraData(
final Collection<? extends RunnableNode> validators,
final String genesisTemplate,
final Function<List<Address>, String> extraDataCreator) {
final List<Address> addresses =
validators.stream().map(RunnableNode::getAddress).collect(toList());
final String extraDataString = extraDataCreator.apply(addresses);
final String genesis = genesisTemplate.replaceAll("%extraData%", extraDataString);
return Optional.of(genesis);
}
private String genesisTemplateConfig(final String template) {
try {
URI uri = Resources.getResource(template).toURI();
return Resources.toString(uri.toURL(), Charset.defaultCharset());
} catch (URISyntaxException | IOException e) {
throw new IllegalStateException("Unable to get test genesis config " + template);
}
}
private Optional<String> createGenesisConfigForValidators(
final Collection<String> validators,
final Collection<? extends RunnableNode> pantheonNodes,
final GenesisConfigProvider genesisConfigProvider) {
final List<RunnableNode> nodes =
pantheonNodes.stream().filter(n -> validators.contains(n.getName())).collect(toList());
return createCliqueGenesisConfig(collect);
return genesisConfigProvider.createGenesisConfig(nodes);
}
private JsonRpcConfiguration jsonRpcConfigWithClique() {
final JsonRpcConfiguration jsonRpcConfig = createJsonRpcEnabledConfig();
final List<RpcApi> rpcApis = new ArrayList<>(jsonRpcConfig.getRpcApis());
rpcApis.add(CLIQUE);
jsonRpcConfig.setRpcApis(rpcApis);
return jsonRpcConfig;
private JsonRpcConfiguration createJsonRpcConfigWithClique() {
return createJsonRpcConfigWithRpcApiEnabled(CLIQUE);
}
private JsonRpcConfiguration createJsonRpcConfigWithIbft() {
return createJsonRpcConfigWithRpcApiEnabled(IBFT);
}
private JsonRpcConfiguration createJsonRpcEnabledConfig() {
@ -254,9 +304,13 @@ public class PantheonNodeFactory {
}
private JsonRpcConfiguration jsonRpcConfigWithPermissioning() {
return createJsonRpcConfigWithRpcApiEnabled(RpcApis.PERM);
}
private JsonRpcConfiguration createJsonRpcConfigWithRpcApiEnabled(final RpcApi rpcApi) {
final JsonRpcConfiguration jsonRpcConfig = createJsonRpcEnabledConfig();
final List<RpcApi> rpcApis = new ArrayList<>(jsonRpcConfig.getRpcApis());
rpcApis.add(RpcApis.PERM);
rpcApis.add(rpcApi);
jsonRpcConfig.setRpcApis(rpcApis);
return jsonRpcConfig;
}

@ -0,0 +1,112 @@
/*
* Copyright 2018 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.
*/
package tech.pegasys.pantheon.tests.acceptance.ibft;
import tech.pegasys.pantheon.tests.acceptance.dsl.AcceptanceTestBase;
import tech.pegasys.pantheon.tests.acceptance.dsl.account.Account;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNode;
import java.io.IOException;
import java.util.List;
import org.junit.Ignore;
import org.junit.Test;
public class IbftMiningAcceptanceTest extends AcceptanceTestBase {
@Test
public void shouldMineOnSingleNode() throws IOException {
final PantheonNode minerNode = pantheon.createIbftNode("miner1");
cluster.start(minerNode);
final Account sender = accounts.createAccount("account1");
final Account receiver = accounts.createAccount("account2");
minerNode.execute(transactions.createTransfer(sender, 50));
cluster.verify(sender.balanceEquals(50));
minerNode.execute(transactions.createIncrementalTransfers(sender, receiver, 1));
cluster.verify(receiver.balanceEquals(1));
minerNode.execute(transactions.createIncrementalTransfers(sender, receiver, 2));
cluster.verify(receiver.balanceEquals(3));
}
@Test
public void shouldMineOnMultipleNodes() throws IOException {
final PantheonNode minerNode1 = pantheon.createIbftNode("miner1");
final PantheonNode minerNode2 = pantheon.createIbftNode("miner2");
final PantheonNode minerNode3 = pantheon.createIbftNode("miner3");
final PantheonNode minerNode4 = pantheon.createIbftNode("miner4");
cluster.start(minerNode1, minerNode2, minerNode3, minerNode4);
final Account sender = accounts.createAccount("account1");
final Account receiver = accounts.createAccount("account2");
minerNode1.execute(transactions.createTransfer(sender, 50));
cluster.verify(sender.balanceEquals(50));
minerNode2.execute(transactions.createIncrementalTransfers(sender, receiver, 1));
cluster.verify(receiver.balanceEquals(1));
minerNode3.execute(transactions.createIncrementalTransfers(sender, receiver, 2));
cluster.verify(receiver.balanceEquals(3));
minerNode4.execute(transactions.createIncrementalTransfers(sender, receiver, 3));
cluster.verify(receiver.balanceEquals(6));
}
@Test
@Ignore("Temporarily disabled until ibft new block events are being sent")
public void shouldMineOnMultipleNodesEvenWhenClusterContainsNonValidator() throws IOException {
final String[] validators = {"validator1", "validator2", "validator3"};
final PantheonNode validator1 = pantheon.createIbftNodeWithValidators("validator1", validators);
final PantheonNode validator2 = pantheon.createIbftNodeWithValidators("validator2", validators);
final PantheonNode validator3 = pantheon.createIbftNodeWithValidators("validator3", validators);
final PantheonNode nonValidatorNode =
pantheon.createIbftNodeWithValidators("non-validator", validators);
cluster.start(validator1, validator2, validator3, nonValidatorNode);
final Account sender = accounts.createAccount("account1");
final Account receiver = accounts.createAccount("account2");
validator1.execute(transactions.createTransfer(sender, 50));
cluster.verify(sender.balanceEquals(50));
validator2.execute(transactions.createIncrementalTransfers(sender, receiver, 1));
cluster.verify(receiver.balanceEquals(1));
nonValidatorNode.execute(transactions.createIncrementalTransfers(sender, receiver, 2));
cluster.verify(receiver.balanceEquals(3));
}
@Test
public void shouldStillMineWhenANonProposerNodeFailsAndHasSufficientValidators()
throws IOException {
final PantheonNode minerNode1 = pantheon.createIbftNode("miner1");
final PantheonNode minerNode2 = pantheon.createIbftNode("miner2");
final PantheonNode minerNode3 = pantheon.createIbftNode("miner3");
final PantheonNode minerNode4 = pantheon.createIbftNode("miner4");
final List<PantheonNode> validators =
ibft.validators(new PantheonNode[] {minerNode1, minerNode2, minerNode3, minerNode4});
final PantheonNode nonProposerNode = validators.get(validators.size() - 1);
cluster.start(validators);
final Account receiver = accounts.createAccount("account2");
cluster.stopNode(nonProposerNode);
validators.get(0).execute(transactions.createTransfer(receiver, 80));
cluster.verifyOnActiveNodes(receiver.balanceEquals(80));
}
}

@ -14,7 +14,7 @@
},
"nonce": "0x0",
"timestamp": "0x58ee40ba",
"extraData": "%cliqueExtraData%",
"extraData": "%extraData%",
"gasLimit": "0x47b760",
"difficulty": "0x1",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",

@ -0,0 +1,43 @@
{
"config": {
"chainId": 4,
"homesteadBlock": 1,
"eip150Block": 2,
"eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"eip155Block": 3,
"eip158Block": 3,
"byzantiumBlock": 1035301,
"revisedibft": {
"blockperiodseconds": 1,
"epochlength": 30000,
"requesttimeoutseconds": 5
}
},
"nonce": "0x0",
"timestamp": "0x58ee40ba",
"extraData": "%extraData%",
"gasLimit": "0x47b760",
"difficulty": "0x1",
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
"coinbase": "0x0000000000000000000000000000000000000000",
"alloc": {
"fe3b557e8fb62b89f4916b721be55ceb828dbd73": {
"privateKey": "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63",
"comment": "private key and this comment are ignored. In a real chain, the private key should NOT be stored",
"balance": "0xad78ebc5ac6200000"
},
"627306090abaB3A6e1400e9345bC60c78a8BEf57": {
"privateKey": "c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3",
"comment": "private key and this comment are ignored. In a real chain, the private key should NOT be stored",
"balance": "90000000000000000000000"
},
"f17f52151EbEF6C7334FAD080c5704D77216b732": {
"privateKey": "ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f",
"comment": "private key and this comment are ignored. In a real chain, the private key should NOT be stored",
"balance": "90000000000000000000000"
}
},
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}

@ -23,6 +23,7 @@ import tech.pegasys.pantheon.ethereum.rlp.RLPInput;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@ -123,6 +124,17 @@ public class IbftExtraData {
return encoder.encoded();
}
public static String createGenesisExtraDataString(final List<Address> validators) {
final IbftExtraData extraData =
new IbftExtraData(
BytesValue.wrap(new byte[32]),
Collections.emptyList(),
Optional.empty(),
0,
validators);
return extraData.encode().toString();
}
// Accessors
public BytesValue getVanityData() {
return vanityData;

Loading…
Cancel
Save