Clique acceptance tests (#290)

Jason Frame 6 years ago committed by GitHub
parent 24b8d730e4
commit ab744a916c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      acceptance-tests/build.gradle
  2. 49
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/clique/CliqueDiscardRpcAcceptanceTest.java
  3. 68
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/clique/CliqueGetSignersRpcTest.java
  4. 75
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/clique/CliqueMiningAcceptanceTest.java
  5. 45
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/clique/CliqueProposalRpcAcceptanceTest.java
  6. 115
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/clique/CliqueProposeRpcAcceptanceTest.java
  7. 9
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/AcceptanceTestBase.java
  8. 7
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/WaitUtils.java
  9. 6
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/blockchain/Blockchain.java
  10. 36
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/blockchain/ExpectBeneficiary.java
  11. 76
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/blockchain/ExpectBlockNotCreated.java
  12. 37
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/blockchain/ExpectBlockNumber.java
  13. 123
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/clique/CliqueConditions.java
  14. 43
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/clique/ExpectNonceVote.java
  15. 38
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/clique/ExpectProposals.java
  16. 39
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/clique/ExpectValidators.java
  17. 42
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/clique/ExpectValidatorsAtBlock.java
  18. 43
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/clique/ExpectValidatorsAtBlockHash.java
  19. 21
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/Cluster.java
  20. 21
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/GenesisConfigProvider.java
  21. 4
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/Node.java
  22. 8
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/NodeConfiguration.java
  23. 68
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java
  24. 109
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNodeFactory.java
  25. 25
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ProcessPantheonNodeRunner.java
  26. 3
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/RunnableNode.java
  27. 7
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ThreadPantheonNodeRunner.java
  28. 2
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/DeploySmartContractTransaction.java
  29. 81
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/PantheonWeb3j.java
  30. 4
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/Transaction.java
  31. 4
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/account/TransferTransaction.java
  32. 5
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/account/TransferTransactionSet.java
  33. 41
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/clique/CliqueDiscard.java
  34. 43
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/clique/CliqueGetSigners.java
  35. 44
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/clique/CliqueGetSignersAtHash.java
  36. 38
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/clique/CliqueProposals.java
  37. 43
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/clique/CliquePropose.java
  38. 48
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/clique/CliqueTransactions.java
  39. 4
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eth/EthAccountsTransaction.java
  40. 5
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eth/EthBlockNumberTransaction.java
  41. 4
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eth/EthGetBalanceTransaction.java
  42. 48
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eth/EthGetBlockTransaction.java
  43. 4
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eth/EthGetTransactionReceiptTransaction.java
  44. 4
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eth/EthGetWorkTransaction.java
  45. 11
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eth/EthTransactions.java
  46. 4
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/net/NetPeerCountTransaction.java
  47. 4
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/net/NetVersionTransaction.java
  48. 4
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/web3/Web3Sha3Transaction.java
  49. 20
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/waitcondition/WaitCondition.java
  50. 48
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/waitcondition/WaitConditions.java
  51. 42
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/waitcondition/WaitUntilSignersChanged.java
  52. 42
      acceptance-tests/src/test/resources/clique/clique.json
  53. 15
      consensus/clique/src/main/java/tech/pegasys/pantheon/consensus/clique/CliqueExtraData.java
  54. 22
      consensus/clique/src/test/java/tech/pegasys/pantheon/consensus/clique/CliqueExtraDataTest.java
  55. 31
      pantheon/src/main/java/tech/pegasys/pantheon/cli/EthNetworkConfig.java
  56. 13
      pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java
  57. 6
      pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonControllerBuilder.java
  58. 47
      pantheon/src/test/java/tech/pegasys/pantheon/cli/PantheonCommandTest.java
  59. 2
      pantheon/src/test/resources/complete_config.toml

@ -22,6 +22,8 @@ dependencies {
testImplementation project(':ethereum:blockcreation') testImplementation project(':ethereum:blockcreation')
testImplementation project(':ethereum:jsonrpc') testImplementation project(':ethereum:jsonrpc')
testImplementation project(':pantheon') testImplementation project(':pantheon')
testImplementation project(':config')
testImplementation project(':consensus:clique')
testImplementation project(':util') testImplementation project(':util')
testImplementation project(path: ':ethereum:core', configuration: 'testSupportArtifacts') testImplementation project(path: ':ethereum:core', configuration: 'testSupportArtifacts')

@ -0,0 +1,49 @@
/*
* 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.clique;
import tech.pegasys.pantheon.tests.acceptance.dsl.AcceptanceTestBase;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNode;
import java.io.IOException;
import org.junit.Test;
public class CliqueDiscardRpcAcceptanceTest extends AcceptanceTestBase {
@Test
public void shouldDiscardVotes() throws IOException {
final String[] initialValidators = {"miner1", "miner2"};
final PantheonNode minerNode1 =
pantheon.createCliqueNodeWithValidators("miner1", initialValidators);
final PantheonNode minerNode2 =
pantheon.createCliqueNodeWithValidators("miner2", initialValidators);
final PantheonNode minerNode3 =
pantheon.createCliqueNodeWithValidators("miner3", initialValidators);
cluster.start(minerNode1, minerNode2, minerNode3);
minerNode1.execute(cliqueTransactions.createRemoveProposal(minerNode2));
minerNode2.execute(cliqueTransactions.createRemoveProposal(minerNode2));
minerNode1.execute(cliqueTransactions.createAddProposal(minerNode3));
minerNode2.execute(cliqueTransactions.createAddProposal(minerNode3));
minerNode1.execute(cliqueTransactions.createDiscardProposal(minerNode2));
minerNode1.execute(cliqueTransactions.createDiscardProposal(minerNode3));
minerNode1.waitUntil(wait.chainHeadHasProgressed(minerNode1, 2));
cluster.verify(clique.validatorsEqual(minerNode1, minerNode2));
minerNode1.verify(clique.noProposals());
minerNode2.verify(
clique.proposalsEqual().removeProposal(minerNode2).addProposal(minerNode3).build());
}
}

@ -0,0 +1,68 @@
/*
* 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.clique;
import static tech.pegasys.pantheon.tests.acceptance.dsl.transaction.clique.CliqueTransactions.LATEST;
import tech.pegasys.pantheon.tests.acceptance.dsl.AcceptanceTestBase;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNode;
import org.junit.Before;
import org.junit.Test;
public class CliqueGetSignersRpcTest extends AcceptanceTestBase {
private PantheonNode minerNode1;
private PantheonNode minerNode2;
private PantheonNode minerNode3;
private PantheonNode[] allNodes;
private PantheonNode[] initialNodes;
@Before
public void setUp() throws Exception {
final String[] validators = {"miner1", "miner2"};
minerNode1 = pantheon.createCliqueNodeWithValidators("miner1", validators);
minerNode2 = pantheon.createCliqueNodeWithValidators("miner2", validators);
minerNode3 = pantheon.createCliqueNodeWithValidators("miner3", validators);
initialNodes = new PantheonNode[] {minerNode1, minerNode2};
allNodes = new PantheonNode[] {minerNode1, minerNode2, minerNode3};
cluster.start(allNodes);
}
@Test
public void shouldBeAbleToGetValidatorsForBlockNumber() {
cluster.verify(clique.validatorsAtBlockEqual("0x0", initialNodes));
cluster.waitUntil(wait.chainHeadIsAt(1));
minerNode1.execute(cliqueTransactions.createAddProposal(minerNode3));
minerNode2.execute(cliqueTransactions.createAddProposal(minerNode3));
cluster.waitUntil(wait.chainHeadHasProgressed(minerNode1, 1));
cluster.verify(clique.validatorsAtBlockEqual("0x2", initialNodes));
minerNode1.waitUntil(wait.chainHeadHasProgressed(minerNode1, 1));
cluster.verify(clique.validatorsAtBlockEqual("0x3", allNodes));
cluster.verify(clique.validatorsAtBlockEqual(LATEST, allNodes));
}
@Test
public void shouldBeAbleToGetValidatorsForBlockHash() {
cluster.verify(clique.validatorsAtBlockHashFromBlockNumberEqual(minerNode1, 0, initialNodes));
minerNode1.execute(cliqueTransactions.createAddProposal(minerNode3));
minerNode2.execute(cliqueTransactions.createAddProposal(minerNode3));
minerNode1.waitUntil(wait.chainHeadHasProgressed(minerNode1, 1));
cluster.verify(clique.validatorsAtBlockHashFromBlockNumberEqual(minerNode1, 1, initialNodes));
minerNode1.waitUntil(wait.chainHeadHasProgressed(minerNode1, 1));
cluster.verify(clique.validatorsAtBlockHashFromBlockNumberEqual(minerNode1, 3, allNodes));
}
}

@ -0,0 +1,75 @@
/*
* 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.clique;
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 org.junit.Test;
public class CliqueMiningAcceptanceTest extends AcceptanceTestBase {
@Test
public void shouldMineTransactionsOnSingleNode() throws IOException {
final PantheonNode minerNode = pantheon.createCliqueNode("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 shouldMineTransactionsOnMultipleNodes() throws IOException {
final PantheonNode minerNode1 = pantheon.createCliqueNode("miner1");
final PantheonNode minerNode2 = pantheon.createCliqueNode("miner2");
final PantheonNode minerNode3 = pantheon.createCliqueNode("miner3");
cluster.start(minerNode1, minerNode2, minerNode3);
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));
}
@Test
public void shouldStallMiningWhenInsufficientValidators() throws IOException {
final PantheonNode minerNode1 = pantheon.createCliqueNode("miner1");
final PantheonNode minerNode2 = pantheon.createCliqueNode("miner2");
final PantheonNode minerNode3 = pantheon.createCliqueNode("miner3");
cluster.start(minerNode1, minerNode2, minerNode3);
cluster.stopNode(minerNode2);
cluster.stopNode(minerNode3);
minerNode1.verify(net.awaitPeerCount(0));
minerNode1.verify(clique.noNewBlockCreated(minerNode1));
}
}

@ -0,0 +1,45 @@
/*
* 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.clique;
import tech.pegasys.pantheon.tests.acceptance.dsl.AcceptanceTestBase;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNode;
import java.io.IOException;
import org.junit.Test;
public class CliqueProposalRpcAcceptanceTest extends AcceptanceTestBase {
@Test
public void shouldReturnProposals() throws IOException {
final String[] initialValidators = {"miner1", "miner2"};
final PantheonNode minerNode1 =
pantheon.createCliqueNodeWithValidators("miner1", initialValidators);
final PantheonNode minerNode2 =
pantheon.createCliqueNodeWithValidators("miner2", initialValidators);
final PantheonNode minerNode3 =
pantheon.createCliqueNodeWithValidators("miner3", initialValidators);
cluster.start(minerNode1, minerNode2, minerNode3);
cluster.verify(clique.noProposals());
minerNode1.execute(cliqueTransactions.createAddProposal(minerNode3));
minerNode1.execute(cliqueTransactions.createRemoveProposal(minerNode2));
minerNode2.execute(cliqueTransactions.createRemoveProposal(minerNode3));
minerNode1.verify(
clique.proposalsEqual().addProposal(minerNode3).removeProposal(minerNode2).build());
minerNode2.verify(clique.proposalsEqual().removeProposal(minerNode3).build());
minerNode3.verify(clique.noProposals());
}
}

@ -0,0 +1,115 @@
/*
* 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.clique;
import tech.pegasys.pantheon.tests.acceptance.dsl.AcceptanceTestBase;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.clique.ExpectNonceVote.CLIQUE_NONCE_VOTE;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNode;
import tech.pegasys.pantheon.tests.acceptance.dsl.waitcondition.WaitCondition;
import java.io.IOException;
import org.junit.Test;
public class CliqueProposeRpcAcceptanceTest extends AcceptanceTestBase {
@Test
public void shouldAddValidators() throws IOException {
final String[] initialValidators = {"miner1", "miner2"};
final PantheonNode minerNode1 =
pantheon.createCliqueNodeWithValidators("miner1", initialValidators);
final PantheonNode minerNode2 =
pantheon.createCliqueNodeWithValidators("miner2", initialValidators);
final PantheonNode minerNode3 =
pantheon.createCliqueNodeWithValidators("miner3", initialValidators);
cluster.start(minerNode1, minerNode2, minerNode3);
cluster.verify(clique.validatorsEqual(minerNode1, minerNode2));
final WaitCondition cliqueValidatorsChanged = wait.cliqueValidatorsChanged(minerNode1);
minerNode1.execute(cliqueTransactions.createAddProposal(minerNode3));
minerNode2.execute(cliqueTransactions.createAddProposal(minerNode3));
cluster.waitUntil(cliqueValidatorsChanged);
cluster.verify(clique.validatorsEqual(minerNode1, minerNode2, minerNode3));
}
@Test
public void shouldRemoveValidators() throws IOException {
final String[] initialValidators = {"miner1", "miner2", "miner3"};
final PantheonNode minerNode1 =
pantheon.createCliqueNodeWithValidators("miner1", initialValidators);
final PantheonNode minerNode2 =
pantheon.createCliqueNodeWithValidators("miner2", initialValidators);
final PantheonNode minerNode3 =
pantheon.createCliqueNodeWithValidators("miner3", initialValidators);
cluster.start(minerNode1, minerNode2, minerNode3);
cluster.verify(clique.validatorsEqual(minerNode1, minerNode2, minerNode3));
final WaitCondition cliqueValidatorsChanged = wait.cliqueValidatorsChanged(minerNode1);
minerNode1.execute(cliqueTransactions.createRemoveProposal(minerNode3));
minerNode2.execute(cliqueTransactions.createRemoveProposal(minerNode3));
cluster.waitUntil(cliqueValidatorsChanged);
cluster.verify(clique.validatorsEqual(minerNode1, minerNode2));
}
@Test
public void shouldNotAddValidatorWhenInsufficientVotes() throws IOException {
final String[] initialValidators = {"miner1", "miner2"};
final PantheonNode minerNode1 =
pantheon.createCliqueNodeWithValidators("miner1", initialValidators);
final PantheonNode minerNode2 =
pantheon.createCliqueNodeWithValidators("miner2", initialValidators);
final PantheonNode minerNode3 =
pantheon.createCliqueNodeWithValidators("miner3", initialValidators);
cluster.start(minerNode1, minerNode2, minerNode3);
cluster.verify(clique.validatorsEqual(minerNode1, minerNode2));
minerNode1.execute(cliqueTransactions.createAddProposal(minerNode3));
minerNode1.waitUntil(wait.chainHeadHasProgressed(minerNode1, 1));
cluster.verify(clique.validatorsEqual(minerNode1, minerNode2));
}
@Test
public void shouldNotRemoveValidatorWhenInsufficientVotes() throws IOException {
final PantheonNode minerNode1 = pantheon.createCliqueNode("miner1");
final PantheonNode minerNode2 = pantheon.createCliqueNode("miner2");
final PantheonNode minerNode3 = pantheon.createCliqueNode("miner3");
cluster.start(minerNode1, minerNode2, minerNode3);
cluster.verify(clique.validatorsEqual(minerNode1, minerNode2, minerNode3));
minerNode1.execute(cliqueTransactions.createRemoveProposal(minerNode3));
minerNode1.waitUntil(wait.chainHeadHasProgressed(minerNode1, 1));
cluster.verify(clique.validatorsEqual(minerNode1, minerNode2, minerNode3));
}
@Test
public void shouldIncludeVoteInBlockHeader() throws IOException {
final String[] initialValidators = {"miner1", "miner2"};
final PantheonNode minerNode1 =
pantheon.createCliqueNodeWithValidators("miner1", initialValidators);
final PantheonNode minerNode2 =
pantheon.createCliqueNodeWithValidators("miner2", initialValidators);
final PantheonNode minerNode3 =
pantheon.createCliqueNodeWithValidators("miner3", initialValidators);
cluster.start(minerNode1, minerNode2, minerNode3);
minerNode1.execute(cliqueTransactions.createAddProposal(minerNode3));
minerNode1.waitUntil(wait.chainHeadHasProgressed(minerNode1, 1));
minerNode1.verify(blockchain.beneficiaryEquals(minerNode3));
minerNode1.verify(clique.nonceVoteEquals(CLIQUE_NONCE_VOTE.AUTH));
minerNode1.execute(cliqueTransactions.createRemoveProposal(minerNode2));
minerNode1.waitUntil(wait.chainHeadHasProgressed(minerNode1, 1));
minerNode1.verify(blockchain.beneficiaryEquals(minerNode2));
minerNode1.verify(clique.nonceVoteEquals(CLIQUE_NONCE_VOTE.DROP));
}
}

@ -14,6 +14,7 @@ package tech.pegasys.pantheon.tests.acceptance.dsl;
import tech.pegasys.pantheon.tests.acceptance.dsl.account.Accounts; 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.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.contract.ContractVerifier;
import tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc.Eth; import tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc.Eth;
import tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc.Net; import tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc.Net;
@ -21,9 +22,11 @@ import tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc.Web3;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.Cluster; import tech.pegasys.pantheon.tests.acceptance.dsl.node.Cluster;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNodeFactory; import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNodeFactory;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transactions; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transactions;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.clique.CliqueTransactions;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eth.EthTransactions; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eth.EthTransactions;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.net.NetTransactions; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.net.NetTransactions;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.web3.Web3Transactions; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.web3.Web3Transactions;
import tech.pegasys.pantheon.tests.acceptance.dsl.waitcondition.WaitConditions;
import org.junit.After; import org.junit.After;
@ -32,24 +35,30 @@ public class AcceptanceTestBase {
protected final Accounts accounts; protected final Accounts accounts;
protected final Blockchain blockchain; protected final Blockchain blockchain;
protected final Cluster cluster; protected final Cluster cluster;
protected final CliqueConditions clique;
protected final CliqueTransactions cliqueTransactions;
protected final Transactions transactions; protected final Transactions transactions;
protected final Web3 web3; protected final Web3 web3;
protected final Eth eth; protected final Eth eth;
protected final Net net; protected final Net net;
protected final PantheonNodeFactory pantheon; protected final PantheonNodeFactory pantheon;
protected final ContractVerifier contractVerifier; protected final ContractVerifier contractVerifier;
protected final WaitConditions wait;
protected AcceptanceTestBase() { protected AcceptanceTestBase() {
final EthTransactions ethTransactions = new EthTransactions(); final EthTransactions ethTransactions = new EthTransactions();
accounts = new Accounts(ethTransactions); accounts = new Accounts(ethTransactions);
blockchain = new Blockchain(ethTransactions); blockchain = new Blockchain(ethTransactions);
eth = new Eth(ethTransactions); eth = new Eth(ethTransactions);
cliqueTransactions = new CliqueTransactions();
clique = new CliqueConditions(ethTransactions, cliqueTransactions);
net = new Net(new NetTransactions()); net = new Net(new NetTransactions());
cluster = new Cluster(net); cluster = new Cluster(net);
transactions = new Transactions(accounts); transactions = new Transactions(accounts);
web3 = new Web3(new Web3Transactions()); web3 = new Web3(new Web3Transactions());
pantheon = new PantheonNodeFactory(); pantheon = new PantheonNodeFactory();
contractVerifier = new ContractVerifier(accounts.getPrimaryBenefactor()); contractVerifier = new ContractVerifier(accounts.getPrimaryBenefactor());
wait = new WaitConditions(ethTransactions, cliqueTransactions);
} }
@After @After

@ -21,4 +21,11 @@ public class WaitUtils {
public static void waitFor(final ThrowingRunnable condition) { public static void waitFor(final ThrowingRunnable condition) {
Awaitility.await().ignoreExceptions().atMost(30, TimeUnit.SECONDS).untilAsserted(condition); Awaitility.await().ignoreExceptions().atMost(30, TimeUnit.SECONDS).untilAsserted(condition);
} }
public static void waitFor(final int timeout, final ThrowingRunnable condition) {
Awaitility.await()
.ignoreExceptions()
.atMost(timeout, TimeUnit.SECONDS)
.untilAsserted(condition);
}
} }

@ -13,8 +13,10 @@
package tech.pegasys.pantheon.tests.acceptance.dsl.blockchain; package tech.pegasys.pantheon.tests.acceptance.dsl.blockchain;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition; import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.blockchain.ExpectBeneficiary;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.blockchain.ExpectMinimumBlockNumber; import tech.pegasys.pantheon.tests.acceptance.dsl.condition.blockchain.ExpectMinimumBlockNumber;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.Node; 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.eth.EthTransactions; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eth.EthTransactions;
public class Blockchain { public class Blockchain {
@ -28,4 +30,8 @@ public class Blockchain {
public Condition blockNumberMustBeLatest(final Node node) { public Condition blockNumberMustBeLatest(final Node node) {
return new ExpectMinimumBlockNumber(eth, node.execute(eth.blockNumber())); return new ExpectMinimumBlockNumber(eth, node.execute(eth.blockNumber()));
} }
public Condition beneficiaryEquals(final PantheonNode node) {
return new ExpectBeneficiary(eth, node);
}
} }

@ -0,0 +1,36 @@
/*
* 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.dsl.condition.blockchain;
import static org.assertj.core.api.Java6Assertions.assertThat;
import static tech.pegasys.pantheon.tests.acceptance.dsl.WaitUtils.waitFor;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition;
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.eth.EthTransactions;
public class ExpectBeneficiary implements Condition {
private final EthTransactions eth;
private final String beneficiary;
public ExpectBeneficiary(final EthTransactions eth, final PantheonNode node) {
this.eth = eth;
this.beneficiary = node.getAddress().toString();
}
@Override
public void verify(final Node node) {
waitFor(() -> assertThat(node.execute(eth.block()).getMiner()).isEqualTo(beneficiary));
}
}

@ -0,0 +1,76 @@
/*
* 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.dsl.condition.blockchain;
import static junit.framework.TestCase.fail;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.Node;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eth.EthTransactions;
import java.math.BigInteger;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.awaitility.Awaitility;
public class ExpectBlockNotCreated implements Condition {
private final EthTransactions eth;
private final int initialWait;
private final int blockPeriodWait;
public ExpectBlockNotCreated(
final EthTransactions eth, final int initialWait, final int blockPeriodWait) {
this.eth = eth;
this.initialWait = initialWait;
this.blockPeriodWait = blockPeriodWait;
}
@Override
public void verify(final Node node) {
final AtomicInteger tries = new AtomicInteger();
final int triesRequired = blockPeriodWait / 1000;
final BigInteger initialBlock = initialBlock(node);
Awaitility.await()
.pollInterval(1, TimeUnit.SECONDS)
.atMost(30, TimeUnit.SECONDS)
.until(() -> isNewBlock(node, tries, triesRequired, initialBlock));
}
private Boolean isNewBlock(
final Node node,
final AtomicInteger tries,
final int triesRequired,
final BigInteger lastBlock) {
final BigInteger currentBlock = node.execute(eth.blockNumber());
final int currentTries = tries.getAndIncrement();
if (!currentBlock.equals(lastBlock)) {
final String failMsg =
String.format(
"New block created. Expected block=%s got block=%s", lastBlock, currentBlock);
fail(failMsg);
}
// Only consider that the block is really the same after a period of time or number tries as a
// block could be currently be in the process of being mined
return currentTries > triesRequired;
}
private BigInteger initialBlock(final Node node) {
try {
Thread.sleep(initialWait);
return node.execute(eth.blockNumber());
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
}

@ -0,0 +1,37 @@
/*
* 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.dsl.condition.blockchain;
import static org.assertj.core.api.Java6Assertions.assertThat;
import static tech.pegasys.pantheon.tests.acceptance.dsl.WaitUtils.waitFor;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.Node;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eth.EthTransactions;
import java.math.BigInteger;
public class ExpectBlockNumber implements Condition {
private final EthTransactions eth;
private final BigInteger blockNumber;
public ExpectBlockNumber(final EthTransactions eth, final BigInteger blockNumber) {
this.eth = eth;
this.blockNumber = blockNumber;
}
@Override
public void verify(final Node node) {
waitFor(() -> assertThat(node.execute(eth.blockNumber())).isEqualTo(blockNumber));
}
}

@ -0,0 +1,123 @@
/*
* 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.dsl.condition.clique;
import static java.util.Collections.emptyList;
import static tech.pegasys.pantheon.ethereum.core.Hash.fromHexString;
import tech.pegasys.pantheon.config.CliqueConfigOptions;
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.CLIQUE_NONCE_VOTE;
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;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eth.EthTransactions;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import com.google.common.collect.ImmutableMap;
import org.web3j.protocol.core.DefaultBlockParameter;
public class CliqueConditions {
private final EthTransactions eth;
private final CliqueTransactions clique;
public CliqueConditions(final EthTransactions eth, final CliqueTransactions clique) {
this.eth = eth;
this.clique = clique;
}
public ExpectValidators validatorsEqual(final PantheonNode... validators) {
return new ExpectValidators(clique, validatorAddresses(validators));
}
public Condition validatorsAtBlockEqual(
final String blockNumber, final PantheonNode... validators) {
return new ExpectValidatorsAtBlock(clique, blockNumber, validatorAddresses(validators));
}
public Condition validatorsAtBlockHashFromBlockNumberEqual(
final Node node, final long blockNumber, final PantheonNode... validators) {
final DefaultBlockParameter blockParameter =
DefaultBlockParameter.valueOf(BigInteger.valueOf(blockNumber));
final String blockHash = node.execute(eth.block(blockParameter)).getHash();
return new ExpectValidatorsAtBlockHash(
clique, fromHexString(blockHash), validatorAddresses(validators));
}
public ProposalsConfig proposalsEqual() {
return new ProposalsConfig(clique);
}
public Condition noProposals() {
return new ExpectProposals(clique, ImmutableMap.of());
}
public Condition nonceVoteEquals(final CLIQUE_NONCE_VOTE clique_nonce_vote) {
return new ExpectNonceVote(eth, clique_nonce_vote);
}
public Condition noNewBlockCreated(final PantheonNode node) {
final int blockPeriodSeconds = cliqueBlockPeriod(node);
final int blockPeriodWait = blockPeriodSeconds * 1000;
return new ExpectBlockNotCreated(eth, blockPeriodWait, blockPeriodWait);
}
private int cliqueBlockPeriod(final PantheonNode node) {
final String config = node.genesisConfigProvider().createGenesisConfig(emptyList()).get();
final GenesisConfigFile genesisConfigFile = GenesisConfigFile.fromConfig(config);
final CliqueConfigOptions cliqueConfigOptions =
genesisConfigFile.getConfigOptions().getCliqueConfigOptions();
return cliqueConfigOptions.getBlockPeriodSeconds();
}
private Address[] validatorAddresses(final PantheonNode[] validators) {
return Arrays.stream(validators).map(PantheonNode::getAddress).sorted().toArray(Address[]::new);
}
public static class ProposalsConfig {
private final Map<PantheonNode, Boolean> proposals = new HashMap<>();
private final CliqueTransactions clique;
public ProposalsConfig(final CliqueTransactions clique) {
this.clique = clique;
}
public ProposalsConfig addProposal(final PantheonNode node) {
proposals.put(node, true);
return this;
}
public ProposalsConfig removeProposal(final PantheonNode node) {
proposals.put(node, false);
return this;
}
public Condition build() {
final Map<Address, Boolean> proposalsAsAddress =
this.proposals
.entrySet()
.stream()
.collect(Collectors.toMap(p -> p.getKey().getAddress(), Entry::getValue));
return new ExpectProposals(clique, proposalsAsAddress);
}
}
}

@ -0,0 +1,43 @@
/*
* 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.dsl.condition.clique;
import static org.assertj.core.api.Java6Assertions.assertThat;
import static tech.pegasys.pantheon.tests.acceptance.dsl.WaitUtils.waitFor;
import static tech.pegasys.pantheon.tests.acceptance.dsl.condition.clique.ExpectNonceVote.CLIQUE_NONCE_VOTE.AUTH;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.Node;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eth.EthTransactions;
public class ExpectNonceVote implements Condition {
private static final String NONCE_AUTH = "0xffffffffffffffff";
private static final String NONCE_DROP = "0x0000000000000000";
private final EthTransactions eth;
private final String expectedNonce;
public enum CLIQUE_NONCE_VOTE {
AUTH,
DROP
}
public ExpectNonceVote(final EthTransactions eth, final CLIQUE_NONCE_VOTE vote) {
this.eth = eth;
this.expectedNonce = vote == AUTH ? NONCE_AUTH : NONCE_DROP;
}
@Override
public void verify(final Node node) {
waitFor(() -> assertThat(node.execute(eth.block()).getNonceRaw()).isEqualTo(expectedNonce));
}
}

@ -0,0 +1,38 @@
/*
* 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.dsl.condition.clique;
import static org.assertj.core.api.Assertions.assertThat;
import static tech.pegasys.pantheon.tests.acceptance.dsl.WaitUtils.waitFor;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.Node;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.clique.CliqueTransactions;
import java.util.Map;
public class ExpectProposals implements Condition {
private final CliqueTransactions clique;
private final Map<Address, Boolean> proposers;
public ExpectProposals(final CliqueTransactions clique, final Map<Address, Boolean> proposers) {
this.clique = clique;
this.proposers = proposers;
}
@Override
public void verify(final Node node) {
waitFor(() -> assertThat(node.execute(clique.createProposals())).isEqualTo(proposers));
}
}

@ -0,0 +1,39 @@
/*
* 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.dsl.condition.clique;
import static org.assertj.core.api.Assertions.assertThat;
import static tech.pegasys.pantheon.tests.acceptance.dsl.WaitUtils.waitFor;
import static tech.pegasys.pantheon.tests.acceptance.dsl.transaction.clique.CliqueTransactions.LATEST;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.Node;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.clique.CliqueTransactions;
public class ExpectValidators implements Condition {
private final CliqueTransactions clique;
private final Address[] validators;
public ExpectValidators(final CliqueTransactions clique, final Address... validators) {
this.clique = clique;
this.validators = validators;
}
@Override
public void verify(final Node node) {
waitFor(
() ->
assertThat(node.execute(clique.createGetSigners(LATEST))).containsExactly(validators));
}
}

@ -0,0 +1,42 @@
/*
* 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.dsl.condition.clique;
import static org.assertj.core.api.Assertions.assertThat;
import static tech.pegasys.pantheon.tests.acceptance.dsl.WaitUtils.waitFor;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.Node;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.clique.CliqueTransactions;
public class ExpectValidatorsAtBlock implements Condition {
private final CliqueTransactions clique;
private final String blockParameter;
private final Address[] validators;
public ExpectValidatorsAtBlock(
final CliqueTransactions clique, final String blockNumber, final Address... validators) {
this.clique = clique;
this.blockParameter = blockNumber;
this.validators = validators;
}
@Override
public void verify(final Node node) {
waitFor(
() ->
assertThat(node.execute(clique.createGetSigners(blockParameter)))
.containsExactly(validators));
}
}

@ -0,0 +1,43 @@
/*
* 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.dsl.condition.clique;
import static org.assertj.core.api.Assertions.assertThat;
import static tech.pegasys.pantheon.tests.acceptance.dsl.WaitUtils.waitFor;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.Node;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.clique.CliqueTransactions;
public class ExpectValidatorsAtBlockHash implements Condition {
private final CliqueTransactions clique;
private final Hash blockHash;
private final Address[] validators;
public ExpectValidatorsAtBlockHash(
final CliqueTransactions clique, final Hash blockHash, final Address... validators) {
this.clique = clique;
this.blockHash = blockHash;
this.validators = validators;
}
@Override
public void verify(final Node node) {
waitFor(
() ->
assertThat(node.execute(clique.createGetSignersAtHash(blockHash)))
.containsExactly(validators));
}
}

@ -14,17 +14,21 @@ package tech.pegasys.pantheon.tests.acceptance.dsl.node;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.cli.EthNetworkConfig;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition; import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition;
import tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc.Net; import tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc.Net;
import tech.pegasys.pantheon.tests.acceptance.dsl.waitcondition.WaitCondition;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class Cluster implements AutoCloseable { public class Cluster implements AutoCloseable {
public static final int NETWORK_ID = 10;
private final Map<String, RunnableNode> nodes = new HashMap<>(); private final Map<String, RunnableNode> nodes = new HashMap<>();
private final PantheonNodeRunner pantheonNodeRunner = PantheonNodeRunner.instance(); private final PantheonNodeRunner pantheonNodeRunner = PantheonNodeRunner.instance();
@ -49,6 +53,7 @@ public class Cluster implements AutoCloseable {
this.nodes.clear(); this.nodes.clear();
final List<String> bootNodes = new ArrayList<>(); final List<String> bootNodes = new ArrayList<>();
for (final RunnableNode node : nodes) { for (final RunnableNode node : nodes) {
this.nodes.put(node.getName(), node); this.nodes.put(node.getName(), node);
bootNodes.add(node.getConfiguration().enodeUrl()); bootNodes.add(node.getConfiguration().enodeUrl());
@ -56,6 +61,12 @@ public class Cluster implements AutoCloseable {
for (final RunnableNode node : nodes) { for (final RunnableNode node : nodes) {
node.getConfiguration().bootnodes(bootNodes); node.getConfiguration().bootnodes(bootNodes);
Optional<EthNetworkConfig> ethNetworkConfig =
node.getConfiguration()
.genesisConfigProvider()
.createGenesisConfig(nodes)
.map(config -> new EthNetworkConfig(config, NETWORK_ID, bootNodes));
node.getConfiguration().ethNetworkConfig(ethNetworkConfig);
node.start(pantheonNodeRunner); node.start(pantheonNodeRunner);
} }
@ -71,6 +82,10 @@ public class Cluster implements AutoCloseable {
pantheonNodeRunner.shutdown(); pantheonNodeRunner.shutdown();
} }
public void stopNode(final PantheonNode node) {
pantheonNodeRunner.stopNode(node);
}
@Override @Override
public void close() { public void close() {
for (final RunnableNode node : nodes.values()) { for (final RunnableNode node : nodes.values()) {
@ -84,4 +99,10 @@ public class Cluster implements AutoCloseable {
expected.verify(node); expected.verify(node);
} }
} }
public void waitUntil(final WaitCondition condition) {
for (final Node node : nodes.values()) {
node.waitUntil(condition);
}
}
} }

@ -0,0 +1,21 @@
/*
* 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.dsl.node;
import java.util.List;
import java.util.Optional;
@FunctionalInterface
public interface GenesisConfigProvider {
Optional<String> createGenesisConfig(final List<RunnableNode> validators);
}

@ -14,10 +14,12 @@ package tech.pegasys.pantheon.tests.acceptance.dsl.node;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition; import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction;
import tech.pegasys.pantheon.tests.acceptance.dsl.waitcondition.WaitCondition;
public interface Node { public interface Node {
<T> T execute(Transaction<T> transaction); <T> T execute(Transaction<T> transaction);
void verify(final Condition expected); void verify(final Condition expected);
void waitUntil(final WaitCondition condition);
} }

@ -12,6 +12,8 @@
*/ */
package tech.pegasys.pantheon.tests.acceptance.dsl.node; package tech.pegasys.pantheon.tests.acceptance.dsl.node;
import tech.pegasys.pantheon.cli.EthNetworkConfig;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -28,4 +30,10 @@ public interface NodeConfiguration {
String hostName(); String hostName();
boolean jsonRpcEnabled(); boolean jsonRpcEnabled();
GenesisConfigProvider genesisConfigProvider();
Optional<EthNetworkConfig> ethNetworkConfig();
void ethNetworkConfig(Optional<EthNetworkConfig> ethNetworkConfig);
} }

@ -14,13 +14,18 @@ package tech.pegasys.pantheon.tests.acceptance.dsl.node;
import static org.apache.logging.log4j.LogManager.getLogger; import static org.apache.logging.log4j.LogManager.getLogger;
import tech.pegasys.pantheon.cli.EthNetworkConfig;
import tech.pegasys.pantheon.controller.KeyPairUtil; import tech.pegasys.pantheon.controller.KeyPairUtil;
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.MiningParameters; import tech.pegasys.pantheon.ethereum.core.MiningParameters;
import tech.pegasys.pantheon.ethereum.core.Util;
import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration; import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration;
import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition; import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.PantheonWeb3j;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction;
import tech.pegasys.pantheon.tests.acceptance.dsl.waitcondition.WaitCondition;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
@ -43,7 +48,6 @@ import org.apache.logging.log4j.Logger;
import org.awaitility.Awaitility; import org.awaitility.Awaitility;
import org.awaitility.core.ConditionTimeoutException; import org.awaitility.core.ConditionTimeoutException;
import org.java_websocket.exceptions.WebsocketNotConnectedException; import org.java_websocket.exceptions.WebsocketNotConnectedException;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.http.HttpService; import org.web3j.protocol.http.HttpService;
import org.web3j.protocol.websocket.WebSocketClient; import org.web3j.protocol.websocket.WebSocketClient;
import org.web3j.protocol.websocket.WebSocketListener; import org.web3j.protocol.websocket.WebSocketListener;
@ -64,15 +68,20 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto
private final MiningParameters miningParameters; private final MiningParameters miningParameters;
private final JsonRpcConfiguration jsonRpcConfiguration; private final JsonRpcConfiguration jsonRpcConfiguration;
private final WebSocketConfiguration webSocketConfiguration; private final WebSocketConfiguration webSocketConfiguration;
private final GenesisConfigProvider genesisConfigProvider;
private final boolean devMode;
private List<String> bootnodes = new ArrayList<>(); private List<String> bootnodes = new ArrayList<>();
private Web3j web3j; private PantheonWeb3j pantheonWeb3j;
private Optional<EthNetworkConfig> ethNetworkConfig = Optional.empty();
public PantheonNode( public PantheonNode(
final String name, final String name,
final MiningParameters miningParameters, final MiningParameters miningParameters,
final JsonRpcConfiguration jsonRpcConfiguration, final JsonRpcConfiguration jsonRpcConfiguration,
final WebSocketConfiguration webSocketConfiguration, final WebSocketConfiguration webSocketConfiguration,
final boolean devMode,
final GenesisConfigProvider genesisConfigProvider,
final int p2pPort) final int p2pPort)
throws IOException { throws IOException {
this.homeDirectory = Files.createTempDirectory("acctest"); this.homeDirectory = Files.createTempDirectory("acctest");
@ -82,6 +91,8 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto
this.miningParameters = miningParameters; this.miningParameters = miningParameters;
this.jsonRpcConfiguration = jsonRpcConfiguration; this.jsonRpcConfiguration = jsonRpcConfiguration;
this.webSocketConfiguration = webSocketConfiguration; this.webSocketConfiguration = webSocketConfiguration;
this.genesisConfigProvider = genesisConfigProvider;
this.devMode = devMode;
LOG.info("Created PantheonNode {}", this.toString()); LOG.info("Created PantheonNode {}", this.toString());
} }
@ -138,19 +149,19 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto
return LOCALHOST; return LOCALHOST;
} }
private Web3j web3j() { private PantheonWeb3j pantheonWeb3j() {
if (web3j == null) { if (pantheonWeb3j == null) {
if (!jsonRpcBaseUrl().isPresent()) { if (!jsonRpcBaseUrl().isPresent()) {
return Web3j.build( return new PantheonWeb3j(
new HttpService("http://" + LOCALHOST + ":8545"), 2000, Async.defaultExecutorService()); new HttpService("http://" + LOCALHOST + ":8545"), 2000, Async.defaultExecutorService());
} }
return Web3j.build( return new PantheonWeb3j(
new HttpService(jsonRpcBaseUrl().get()), 2000, Async.defaultExecutorService()); new HttpService(jsonRpcBaseUrl().get()), 2000, Async.defaultExecutorService());
} }
return web3j; return pantheonWeb3j;
} }
/** All future JSON-RPC calls are made via a web sockets connection. */ /** All future JSON-RPC calls are made via a web sockets connection. */
@ -167,11 +178,11 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto
throw new RuntimeException("Error connection to WebSocket endpoint", e); throw new RuntimeException("Error connection to WebSocket endpoint", e);
} }
if (web3j != null) { if (pantheonWeb3j != null) {
web3j.shutdown(); pantheonWeb3j.shutdown();
} }
web3j = Web3j.build(webSocketService, 2000, Async.defaultExecutorService()); pantheonWeb3j = new PantheonWeb3j(webSocketService, 2000, Async.defaultExecutorService());
} }
private void checkIfWebSocketEndpointIsAvailable(final String url) { private void checkIfWebSocketEndpointIsAvailable(final String url) {
@ -235,6 +246,11 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto
} }
} }
@Override
public Address getAddress() {
return Util.publicKeyToAddress(keyPair.getPublicKey());
}
Path homeDirectory() { Path homeDirectory() {
return homeDirectory; return homeDirectory;
} }
@ -293,6 +309,10 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto
return miningParameters; return miningParameters;
} }
public boolean isDevMode() {
return devMode;
}
@Override @Override
public String toString() { public String toString() {
return MoreObjects.toStringHelper(this) return MoreObjects.toStringHelper(this)
@ -305,9 +325,9 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto
@Override @Override
public void stop() { public void stop() {
if (web3j != null) { if (pantheonWeb3j != null) {
web3j.shutdown(); pantheonWeb3j.shutdown();
web3j = null; pantheonWeb3j = null;
} }
} }
@ -321,13 +341,33 @@ public class PantheonNode implements Node, NodeConfiguration, RunnableNode, Auto
} }
} }
@Override
public GenesisConfigProvider genesisConfigProvider() {
return genesisConfigProvider;
}
@Override
public Optional<EthNetworkConfig> ethNetworkConfig() {
return ethNetworkConfig;
}
@Override
public void ethNetworkConfig(final Optional<EthNetworkConfig> ethNetworkConfig) {
this.ethNetworkConfig = ethNetworkConfig;
}
@Override @Override
public <T> T execute(final Transaction<T> transaction) { public <T> T execute(final Transaction<T> transaction) {
return transaction.execute(web3j()); return transaction.execute(pantheonWeb3j());
} }
@Override @Override
public void verify(final Condition expected) { public void verify(final Condition expected) {
expected.verify(this); expected.verify(this);
} }
@Override
public void waitUntil(final WaitCondition expected) {
expected.waitUntil(this);
}
} }

@ -12,6 +12,12 @@
*/ */
package tech.pegasys.pantheon.tests.acceptance.dsl.node; package tech.pegasys.pantheon.tests.acceptance.dsl.node;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.toList;
import static tech.pegasys.pantheon.consensus.clique.jsonrpc.CliqueRpcApis.CLIQUE;
import tech.pegasys.pantheon.consensus.clique.CliqueExtraData;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.MiningParameters; import tech.pegasys.pantheon.ethereum.core.MiningParameters;
import tech.pegasys.pantheon.ethereum.core.MiningParametersTestBuilder; import tech.pegasys.pantheon.ethereum.core.MiningParametersTestBuilder;
import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration; import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration;
@ -20,7 +26,15 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration;
import java.io.IOException; import java.io.IOException;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.util.Arrays; import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import com.google.common.io.Resources;
public class PantheonNodeFactory { public class PantheonNodeFactory {
@ -32,6 +46,8 @@ public class PantheonNodeFactory {
config.getMiningParameters(), config.getMiningParameters(),
config.getJsonRpcConfiguration(), config.getJsonRpcConfiguration(),
config.getWebSocketConfiguration(), config.getWebSocketConfiguration(),
config.isDevMode(),
config.getGenesisConfigProvider(),
serverSocket.getLocalPort()); serverSocket.getLocalPort());
serverSocket.close(); serverSocket.close();
@ -62,15 +78,74 @@ public class PantheonNodeFactory {
public PantheonNode createArchiveNodeWithRpcApis( public PantheonNode createArchiveNodeWithRpcApis(
final String name, final RpcApi... enabledRpcApis) throws IOException { final String name, final RpcApi... enabledRpcApis) throws IOException {
final JsonRpcConfiguration jsonRpcConfig = createJsonRpcConfig(); final JsonRpcConfiguration jsonRpcConfig = createJsonRpcConfig();
jsonRpcConfig.setRpcApis(Arrays.asList(enabledRpcApis)); jsonRpcConfig.setRpcApis(asList(enabledRpcApis));
final WebSocketConfiguration webSocketConfig = createWebSocketConfig(); final WebSocketConfiguration webSocketConfig = createWebSocketConfig();
webSocketConfig.setRpcApis(Arrays.asList(enabledRpcApis)); webSocketConfig.setRpcApis(asList(enabledRpcApis));
return create( return create(
new PantheonFactoryConfiguration( new PantheonFactoryConfiguration(
name, createMiningParameters(false), jsonRpcConfig, webSocketConfig)); name, createMiningParameters(false), jsonRpcConfig, webSocketConfig));
} }
public PantheonNode createCliqueNode(final String name) throws IOException {
return create(
new PantheonFactoryConfiguration(
name,
createMiningParameters(true),
jsonRpcConfigWithClique(),
createWebSocketConfig(),
false,
this::createCliqueGenesisConfig));
}
public PantheonNode createCliqueNodeWithValidators(final String name, final String... validators)
throws IOException {
return create(
new PantheonFactoryConfiguration(
name,
createMiningParameters(true),
jsonRpcConfigWithClique(),
createWebSocketConfig(),
false,
nodes -> createCliqueGenesisConfigForValidators(asList(validators), nodes)));
}
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);
}
private Optional<String> createCliqueGenesisConfigForValidators(
final Collection<String> validators, final Collection<RunnableNode> pantheonNodes) {
List<RunnableNode> collect =
pantheonNodes.stream().filter(n -> validators.contains(n.getName())).collect(toList());
return createCliqueGenesisConfig(collect);
}
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");
}
}
private String encodeCliqueExtraData(final Collection<RunnableNode> nodes) {
final List<Address> addresses = nodes.stream().map(RunnableNode::getAddress).collect(toList());
return CliqueExtraData.createGenesisExtraDataString(addresses);
}
private JsonRpcConfiguration jsonRpcConfigWithClique() {
final JsonRpcConfiguration jsonRpcConfig = createJsonRpcConfig();
final List<RpcApi> rpcApis = new ArrayList<>(jsonRpcConfig.getRpcApis());
rpcApis.add(CLIQUE);
jsonRpcConfig.setRpcApis(rpcApis);
return jsonRpcConfig;
}
private MiningParameters createMiningParameters(final boolean miner) { private MiningParameters createMiningParameters(final boolean miner) {
return new MiningParametersTestBuilder().enabled(miner).build(); return new MiningParametersTestBuilder().enabled(miner).build();
} }
@ -95,16 +170,36 @@ public class PantheonNodeFactory {
private final MiningParameters miningParameters; private final MiningParameters miningParameters;
private final JsonRpcConfiguration jsonRpcConfiguration; private final JsonRpcConfiguration jsonRpcConfiguration;
private final WebSocketConfiguration webSocketConfiguration; private final WebSocketConfiguration webSocketConfiguration;
private final boolean devMode;
private final GenesisConfigProvider genesisConfigProvider;
public PantheonFactoryConfiguration( public PantheonFactoryConfiguration(
final String name, final String name,
final MiningParameters miningParameters, final MiningParameters miningParameters,
final JsonRpcConfiguration jsonRpcConfiguration, final JsonRpcConfiguration jsonRpcConfiguration,
final WebSocketConfiguration webSocketConfiguration) { final WebSocketConfiguration webSocketConfiguration) {
this(
name,
miningParameters,
jsonRpcConfiguration,
webSocketConfiguration,
true,
ignore -> Optional.empty());
}
public PantheonFactoryConfiguration(
final String name,
final MiningParameters miningParameters,
final JsonRpcConfiguration jsonRpcConfiguration,
final WebSocketConfiguration webSocketConfiguration,
final boolean devMode,
final GenesisConfigProvider genesisConfigProvider) {
this.name = name; this.name = name;
this.miningParameters = miningParameters; this.miningParameters = miningParameters;
this.jsonRpcConfiguration = jsonRpcConfiguration; this.jsonRpcConfiguration = jsonRpcConfiguration;
this.webSocketConfiguration = webSocketConfiguration; this.webSocketConfiguration = webSocketConfiguration;
this.devMode = devMode;
this.genesisConfigProvider = genesisConfigProvider;
} }
public String getName() { public String getName() {
@ -122,5 +217,13 @@ public class PantheonNodeFactory {
public WebSocketConfiguration getWebSocketConfiguration() { public WebSocketConfiguration getWebSocketConfiguration() {
return webSocketConfiguration; return webSocketConfiguration;
} }
public boolean isDevMode() {
return devMode;
}
public GenesisConfigProvider getGenesisConfigProvider() {
return genesisConfigProvider;
}
} }
} }

@ -12,11 +12,15 @@
*/ */
package tech.pegasys.pantheon.tests.acceptance.dsl.node; package tech.pegasys.pantheon.tests.acceptance.dsl.node;
import static java.nio.charset.StandardCharsets.UTF_8;
import tech.pegasys.pantheon.cli.EthNetworkConfig;
import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApi; import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApi;
import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApis; import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApis;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -49,7 +53,9 @@ public class ProcessPantheonNodeRunner implements PantheonNodeRunner {
params.add("--datadir"); params.add("--datadir");
params.add(dataDir.toAbsolutePath().toString()); params.add(dataDir.toAbsolutePath().toString());
if (node.isDevMode()) {
params.add("--dev-mode"); params.add("--dev-mode");
}
params.add("--p2p-listen"); params.add("--p2p-listen");
params.add(node.p2pListenAddress()); params.add(node.p2pListenAddress());
@ -79,6 +85,15 @@ public class ProcessPantheonNodeRunner implements PantheonNodeRunner {
params.add(apiList(node.webSocketConfiguration().getRpcApis())); params.add(apiList(node.webSocketConfiguration().getRpcApis()));
} }
if (node.ethNetworkConfig().isPresent()) {
EthNetworkConfig ethNetworkConfig = node.ethNetworkConfig().get();
Path genesisFile = createGenesisFile(node, ethNetworkConfig);
params.add("--genesis");
params.add(genesisFile.toString());
params.add("--network-id");
params.add(Integer.toString(ethNetworkConfig.getNetworkId()));
}
final ProcessBuilder processBuilder = final ProcessBuilder processBuilder =
new ProcessBuilder(params) new ProcessBuilder(params)
.directory(new File(System.getProperty("user.dir")).getParentFile()) .directory(new File(System.getProperty("user.dir")).getParentFile())
@ -94,6 +109,16 @@ public class ProcessPantheonNodeRunner implements PantheonNodeRunner {
waitForPortsFile(dataDir); waitForPortsFile(dataDir);
} }
private Path createGenesisFile(final PantheonNode node, final EthNetworkConfig ethNetworkConfig) {
try {
Path genesisFile = Files.createTempFile(node.homeDirectory(), "gensis", "");
Files.write(genesisFile, ethNetworkConfig.getGenesisConfig().getBytes(UTF_8));
return genesisFile;
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
private String apiList(final Collection<RpcApi> rpcApis) { private String apiList(final Collection<RpcApi> rpcApis) {
return String.join(",", rpcApis.stream().map(RpcApis::getValue).collect(Collectors.toList())); return String.join(",", rpcApis.stream().map(RpcApis::getValue).collect(Collectors.toList()));
} }

@ -12,6 +12,7 @@
*/ */
package tech.pegasys.pantheon.tests.acceptance.dsl.node; package tech.pegasys.pantheon.tests.acceptance.dsl.node;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition; import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition;
public interface RunnableNode extends Node { public interface RunnableNode extends Node {
@ -27,4 +28,6 @@ public interface RunnableNode extends Node {
void awaitPeerDiscovery(final Condition condition); void awaitPeerDiscovery(final Condition condition);
String getName(); String getName();
Address getAddress();
} }

@ -51,8 +51,9 @@ public class ThreadPantheonNodeRunner implements PantheonNodeRunner {
final PantheonControllerBuilder builder = new PantheonControllerBuilder(); final PantheonControllerBuilder builder = new PantheonControllerBuilder();
final EthNetworkConfig ethNetworkConfig = final EthNetworkConfig ethNetworkConfig =
new EthNetworkConfig.Builder(mainnet()).setNetworkId(NETWORK_ID).build(); node.ethNetworkConfig()
final PantheonController<?> pantheonController; .orElse(new EthNetworkConfig.Builder(mainnet()).setNetworkId(NETWORK_ID).build());
PantheonController<?> pantheonController;
try { try {
pantheonController = pantheonController =
builder.build( builder.build(
@ -61,7 +62,7 @@ public class ThreadPantheonNodeRunner implements PantheonNodeRunner {
ethNetworkConfig, ethNetworkConfig,
false, false,
node.getMiningParameters(), node.getMiningParameters(),
true, node.isDevMode(),
KeyPairUtil.getDefaultKeyFile(node.homeDirectory())); KeyPairUtil.getDefaultKeyFile(node.homeDirectory()));
} catch (final IOException e) { } catch (final IOException e) {
throw new RuntimeException("Error building PantheonController", e); throw new RuntimeException("Error building PantheonController", e);

@ -37,7 +37,7 @@ public class DeploySmartContractTransaction<T extends Contract> implements Trans
} }
@Override @Override
public T execute(final Web3j node) { public T execute(final PantheonWeb3j node) {
try { try {
final Method method = final Method method =
clazz.getMethod( clazz.getMethod(

@ -0,0 +1,81 @@
/*
* 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.dsl.transaction;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.Hash;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import org.web3j.protocol.Web3jService;
import org.web3j.protocol.core.JsonRpc2_0Web3j;
import org.web3j.protocol.core.Request;
import org.web3j.protocol.core.Response;
public class PantheonWeb3j extends JsonRpc2_0Web3j {
public PantheonWeb3j(final Web3jService web3jService) {
super(web3jService);
}
public PantheonWeb3j(
final Web3jService web3jService,
final long pollingInterval,
final ScheduledExecutorService scheduledExecutorService) {
super(web3jService, pollingInterval, scheduledExecutorService);
}
public Request<?, ProposeResponse> cliquePropose(final String address, final Boolean auth) {
return new Request<>(
"clique_propose",
Arrays.asList(address, auth.toString()),
web3jService,
ProposeResponse.class);
}
public Request<?, DiscardResponse> cliqueDiscard(final String address) {
return new Request<>(
"clique_discard", singletonList(address), web3jService, DiscardResponse.class);
}
public Request<?, ProposalsResponse> cliqueProposals() {
return new Request<>("clique_proposals", emptyList(), web3jService, ProposalsResponse.class);
}
public Request<?, SignersBlockResponse> cliqueGetSigners(final String blockNumber) {
return new Request<>(
"clique_getSigners", singletonList(blockNumber), web3jService, SignersBlockResponse.class);
}
public Request<?, SignersBlockResponse> cliqueGetSignersAtHash(final Hash hash) {
return new Request<>(
"clique_getSignersAtHash",
singletonList(hash.toString()),
web3jService,
SignersBlockResponse.class);
}
public static class ProposeResponse extends Response<Boolean> {}
public static class DiscardResponse extends Response<Boolean> {}
public static class SignersBlockResponse extends Response<List<Address>> {}
public static class ProposalsResponse extends Response<Map<Address, Boolean>> {}
}

@ -12,9 +12,7 @@
*/ */
package tech.pegasys.pantheon.tests.acceptance.dsl.transaction; package tech.pegasys.pantheon.tests.acceptance.dsl.transaction;
import org.web3j.protocol.Web3j;
public interface Transaction<T> { public interface Transaction<T> {
T execute(final Web3j node); T execute(final PantheonWeb3j node);
} }

@ -16,6 +16,7 @@ import static org.web3j.utils.Numeric.toHexString;
import tech.pegasys.pantheon.ethereum.core.Hash; import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.tests.acceptance.dsl.account.Account; import tech.pegasys.pantheon.tests.acceptance.dsl.account.Account;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.PantheonWeb3j;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction;
import java.io.IOException; import java.io.IOException;
@ -23,7 +24,6 @@ import java.math.BigInteger;
import org.web3j.crypto.RawTransaction; import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.TransactionEncoder; import org.web3j.crypto.TransactionEncoder;
import org.web3j.protocol.Web3j;
import org.web3j.utils.Convert; import org.web3j.utils.Convert;
import org.web3j.utils.Convert.Unit; import org.web3j.utils.Convert.Unit;
@ -46,7 +46,7 @@ public class TransferTransaction implements Transaction<Hash> {
} }
@Override @Override
public Hash execute(final Web3j node) { public Hash execute(final PantheonWeb3j node) {
final RawTransaction transaction = final RawTransaction transaction =
RawTransaction.createEtherTransaction( RawTransaction.createEtherTransaction(
sender.getNextNonce(), sender.getNextNonce(),

@ -13,13 +13,12 @@
package tech.pegasys.pantheon.tests.acceptance.dsl.transaction.account; package tech.pegasys.pantheon.tests.acceptance.dsl.transaction.account;
import tech.pegasys.pantheon.ethereum.core.Hash; import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.PantheonWeb3j;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.web3j.protocol.Web3j;
public class TransferTransactionSet implements Transaction<List<Hash>> { public class TransferTransactionSet implements Transaction<List<Hash>> {
private final List<TransferTransaction> transactions; private final List<TransferTransaction> transactions;
@ -29,7 +28,7 @@ public class TransferTransactionSet implements Transaction<List<Hash>> {
} }
@Override @Override
public List<Hash> execute(final Web3j node) { public List<Hash> execute(final PantheonWeb3j node) {
final List<Hash> hashes = new ArrayList<>(); final List<Hash> hashes = new ArrayList<>();
for (final TransferTransaction transaction : transactions) { for (final TransferTransaction transaction : transactions) {

@ -0,0 +1,41 @@
/*
* 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.dsl.transaction.clique;
import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.PantheonWeb3j;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.PantheonWeb3j.DiscardResponse;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction;
import java.io.IOException;
public class CliqueDiscard implements Transaction<Boolean> {
private final String address;
public CliqueDiscard(final String address) {
this.address = address;
}
@Override
public Boolean execute(final PantheonWeb3j node) {
try {
final DiscardResponse result = node.cliqueDiscard(address).send();
assertThat(result).isNotNull();
assertThat(result.hasError()).isFalse();
return result.getResult();
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
}

@ -0,0 +1,43 @@
/*
* 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.dsl.transaction.clique;
import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.PantheonWeb3j;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.PantheonWeb3j.SignersBlockResponse;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction;
import java.io.IOException;
import java.util.List;
public class CliqueGetSigners implements Transaction<List<Address>> {
private final String blockNumber;
public CliqueGetSigners(final String blockNumber) {
this.blockNumber = blockNumber;
}
@Override
public List<Address> execute(final PantheonWeb3j node) {
try {
final SignersBlockResponse result = node.cliqueGetSigners(blockNumber).send();
assertThat(result).isNotNull();
assertThat(result.hasError()).isFalse();
return result.getResult();
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
}

@ -0,0 +1,44 @@
/*
* 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.dsl.transaction.clique;
import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.PantheonWeb3j;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.PantheonWeb3j.SignersBlockResponse;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction;
import java.io.IOException;
import java.util.List;
public class CliqueGetSignersAtHash implements Transaction<List<Address>> {
private final Hash hash;
public CliqueGetSignersAtHash(final Hash hash) {
this.hash = hash;
}
@Override
public List<Address> execute(final PantheonWeb3j node) {
try {
final SignersBlockResponse result = node.cliqueGetSignersAtHash(hash).send();
assertThat(result).isNotNull();
assertThat(result.hasError()).isFalse();
return result.getResult();
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
}

@ -0,0 +1,38 @@
/*
* 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.dsl.transaction.clique;
import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.PantheonWeb3j;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.PantheonWeb3j.ProposalsResponse;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction;
import java.io.IOException;
import java.util.Map;
public class CliqueProposals implements Transaction<Map<Address, Boolean>> {
@Override
public Map<Address, Boolean> execute(final PantheonWeb3j node) {
try {
final ProposalsResponse result = node.cliqueProposals().send();
assertThat(result).isNotNull();
assertThat(result.hasError()).isFalse();
return result.getResult();
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
}

@ -0,0 +1,43 @@
/*
* 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.dsl.transaction.clique;
import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.PantheonWeb3j;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.PantheonWeb3j.ProposeResponse;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction;
import java.io.IOException;
public class CliquePropose implements Transaction<Boolean> {
private final String address;
private final boolean auth;
public CliquePropose(final String address, final boolean auth) {
this.address = address;
this.auth = auth;
}
@Override
public Boolean execute(final PantheonWeb3j node) {
try {
final ProposeResponse result = node.cliquePropose(address, auth).send();
assertThat(result).isNotNull();
assertThat(result.hasError()).isFalse();
return result.getResult();
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
}

@ -0,0 +1,48 @@
/*
* 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.dsl.transaction.clique;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNode;
public class CliqueTransactions {
public static final String LATEST = "latest";
public CliquePropose createRemoveProposal(final PantheonNode node) {
return propose(node.getAddress().toString(), false);
}
public CliquePropose createAddProposal(final PantheonNode node) {
return propose(node.getAddress().toString(), true);
}
private CliquePropose propose(final String address, final boolean auth) {
return new CliquePropose(address, auth);
}
public CliqueProposals createProposals() {
return new CliqueProposals();
}
public CliqueGetSigners createGetSigners(final String blockNumber) {
return new CliqueGetSigners(blockNumber);
}
public CliqueGetSignersAtHash createGetSignersAtHash(final Hash blockHash) {
return new CliqueGetSignersAtHash(blockHash);
}
public CliqueDiscard createDiscardProposal(final PantheonNode node) {
return new CliqueDiscard(node.getAddress().toString());
}
}

@ -14,12 +14,12 @@ package tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eth;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.PantheonWeb3j;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.methods.response.EthAccounts; import org.web3j.protocol.core.methods.response.EthAccounts;
public class EthAccountsTransaction implements Transaction<List<String>> { public class EthAccountsTransaction implements Transaction<List<String>> {
@ -27,7 +27,7 @@ public class EthAccountsTransaction implements Transaction<List<String>> {
EthAccountsTransaction() {} EthAccountsTransaction() {}
@Override @Override
public List<String> execute(final Web3j node) { public List<String> execute(final PantheonWeb3j node) {
try { try {
final EthAccounts result = node.ethAccounts().send(); final EthAccounts result = node.ethAccounts().send();
assertThat(result).isNotNull(); assertThat(result).isNotNull();

@ -14,19 +14,18 @@ package tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eth;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.PantheonWeb3j;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction;
import java.io.IOException; import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import org.web3j.protocol.Web3j;
public class EthBlockNumberTransaction implements Transaction<BigInteger> { public class EthBlockNumberTransaction implements Transaction<BigInteger> {
EthBlockNumberTransaction() {} EthBlockNumberTransaction() {}
@Override @Override
public BigInteger execute(final Web3j node) { public BigInteger execute(final PantheonWeb3j node) {
try { try {
final org.web3j.protocol.core.methods.response.EthBlockNumber result = final org.web3j.protocol.core.methods.response.EthBlockNumber result =
node.ethBlockNumber().send(); node.ethBlockNumber().send();

@ -16,12 +16,12 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.web3j.protocol.core.DefaultBlockParameterName.LATEST; import static org.web3j.protocol.core.DefaultBlockParameterName.LATEST;
import tech.pegasys.pantheon.tests.acceptance.dsl.account.Account; import tech.pegasys.pantheon.tests.acceptance.dsl.account.Account;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.PantheonWeb3j;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction;
import java.io.IOException; import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.methods.response.EthGetBalance; import org.web3j.protocol.core.methods.response.EthGetBalance;
public class EthGetBalanceTransaction implements Transaction<BigInteger> { public class EthGetBalanceTransaction implements Transaction<BigInteger> {
@ -33,7 +33,7 @@ public class EthGetBalanceTransaction implements Transaction<BigInteger> {
} }
@Override @Override
public BigInteger execute(final Web3j node) { public BigInteger execute(final PantheonWeb3j node) {
try { try {
final EthGetBalance result = node.ethGetBalance(account.getAddress(), LATEST).send(); final EthGetBalance result = node.ethGetBalance(account.getAddress(), LATEST).send();
assertThat(result).isNotNull(); assertThat(result).isNotNull();

@ -0,0 +1,48 @@
/*
* 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.dsl.transaction.eth;
import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.PantheonWeb3j;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction;
import java.io.IOException;
import org.web3j.protocol.core.DefaultBlockParameter;
import org.web3j.protocol.core.methods.response.EthBlock;
import org.web3j.protocol.core.methods.response.EthBlock.Block;
public class EthGetBlockTransaction implements Transaction<Block> {
private final DefaultBlockParameter blockParameter;
private final boolean fullTransactionObjects;
EthGetBlockTransaction(
final DefaultBlockParameter blockParameter, final boolean fullTransactionObjects) {
this.blockParameter = blockParameter;
this.fullTransactionObjects = fullTransactionObjects;
}
@Override
public Block execute(final PantheonWeb3j node) {
try {
final EthBlock result =
node.ethGetBlockByNumber(blockParameter, fullTransactionObjects).send();
assertThat(result).isNotNull();
assertThat(result.hasError()).isFalse();
return result.getBlock();
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
}

@ -14,12 +14,12 @@ package tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eth;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.PantheonWeb3j;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction;
import java.io.IOException; import java.io.IOException;
import java.util.Optional; import java.util.Optional;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.methods.response.EthGetTransactionReceipt; import org.web3j.protocol.core.methods.response.EthGetTransactionReceipt;
import org.web3j.protocol.core.methods.response.TransactionReceipt; import org.web3j.protocol.core.methods.response.TransactionReceipt;
@ -33,7 +33,7 @@ public class EthGetTransactionReceiptTransaction
} }
@Override @Override
public Optional<TransactionReceipt> execute(final Web3j node) { public Optional<TransactionReceipt> execute(final PantheonWeb3j node) {
try { try {
final EthGetTransactionReceipt result = node.ethGetTransactionReceipt(input).send(); final EthGetTransactionReceipt result = node.ethGetTransactionReceipt(input).send();
assertThat(result.hasError()).isFalse(); assertThat(result.hasError()).isFalse();

@ -14,11 +14,11 @@ package tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eth;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.PantheonWeb3j;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction;
import java.io.IOException; import java.io.IOException;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.methods.response.EthGetWork; import org.web3j.protocol.core.methods.response.EthGetWork;
public class EthGetWorkTransaction implements Transaction<String[]> { public class EthGetWorkTransaction implements Transaction<String[]> {
@ -26,7 +26,7 @@ public class EthGetWorkTransaction implements Transaction<String[]> {
EthGetWorkTransaction() {} EthGetWorkTransaction() {}
@Override @Override
public String[] execute(final Web3j node) { public String[] execute(final PantheonWeb3j node) {
try { try {
final EthGetWork result = node.ethGetWork().send(); final EthGetWork result = node.ethGetWork().send();
assertThat(result).isNotNull(); assertThat(result).isNotNull();

@ -14,6 +14,9 @@ package tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eth;
import tech.pegasys.pantheon.tests.acceptance.dsl.account.Account; import tech.pegasys.pantheon.tests.acceptance.dsl.account.Account;
import org.web3j.protocol.core.DefaultBlockParameter;
import org.web3j.protocol.core.DefaultBlockParameterName;
public class EthTransactions { public class EthTransactions {
public EthGetWorkTransaction getWork() { public EthGetWorkTransaction getWork() {
@ -24,6 +27,14 @@ public class EthTransactions {
return new EthBlockNumberTransaction(); return new EthBlockNumberTransaction();
} }
public EthGetBlockTransaction block() {
return block(DefaultBlockParameterName.LATEST);
}
public EthGetBlockTransaction block(final DefaultBlockParameter blockParameter) {
return new EthGetBlockTransaction(blockParameter, false);
}
public EthGetBalanceTransaction getBalance(final Account account) { public EthGetBalanceTransaction getBalance(final Account account) {
return new EthGetBalanceTransaction(account); return new EthGetBalanceTransaction(account);
} }

@ -14,12 +14,12 @@ package tech.pegasys.pantheon.tests.acceptance.dsl.transaction.net;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.PantheonWeb3j;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction;
import java.io.IOException; import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.methods.response.NetPeerCount; import org.web3j.protocol.core.methods.response.NetPeerCount;
public class NetPeerCountTransaction implements Transaction<BigInteger> { public class NetPeerCountTransaction implements Transaction<BigInteger> {
@ -27,7 +27,7 @@ public class NetPeerCountTransaction implements Transaction<BigInteger> {
NetPeerCountTransaction() {} NetPeerCountTransaction() {}
@Override @Override
public BigInteger execute(final Web3j node) { public BigInteger execute(final PantheonWeb3j node) {
try { try {
final NetPeerCount result = node.netPeerCount().send(); final NetPeerCount result = node.netPeerCount().send();
assertThat(result).isNotNull(); assertThat(result).isNotNull();

@ -14,11 +14,11 @@ package tech.pegasys.pantheon.tests.acceptance.dsl.transaction.net;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.PantheonWeb3j;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction;
import java.io.IOException; import java.io.IOException;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.methods.response.NetVersion; import org.web3j.protocol.core.methods.response.NetVersion;
public class NetVersionTransaction implements Transaction<String> { public class NetVersionTransaction implements Transaction<String> {
@ -26,7 +26,7 @@ public class NetVersionTransaction implements Transaction<String> {
NetVersionTransaction() {} NetVersionTransaction() {}
@Override @Override
public String execute(final Web3j node) { public String execute(final PantheonWeb3j node) {
try { try {
final NetVersion result = node.netVersion().send(); final NetVersion result = node.netVersion().send();
assertThat(result).isNotNull(); assertThat(result).isNotNull();

@ -14,11 +14,11 @@ package tech.pegasys.pantheon.tests.acceptance.dsl.transaction.web3;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.PantheonWeb3j;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction;
import java.io.IOException; import java.io.IOException;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.methods.response.Web3Sha3; import org.web3j.protocol.core.methods.response.Web3Sha3;
public class Web3Sha3Transaction implements Transaction<String> { public class Web3Sha3Transaction implements Transaction<String> {
@ -30,7 +30,7 @@ public class Web3Sha3Transaction implements Transaction<String> {
} }
@Override @Override
public String execute(final Web3j node) { public String execute(final PantheonWeb3j node) {
try { try {
final Web3Sha3 result = node.web3Sha3(input).send(); final Web3Sha3 result = node.web3Sha3(input).send();
assertThat(result).isNotNull(); assertThat(result).isNotNull();

@ -0,0 +1,20 @@
/*
* 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.dsl.waitcondition;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.Node;
@FunctionalInterface
public interface WaitCondition {
void waitUntil(Node node);
}

@ -0,0 +1,48 @@
/*
* 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.dsl.waitcondition;
import static tech.pegasys.pantheon.tests.acceptance.dsl.transaction.clique.CliqueTransactions.LATEST;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.blockchain.ExpectBlockNumber;
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;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eth.EthTransactions;
import java.math.BigInteger;
public class WaitConditions {
private final EthTransactions eth;
private final CliqueTransactions clique;
public WaitConditions(final EthTransactions eth, final CliqueTransactions clique) {
this.eth = eth;
this.clique = clique;
}
public WaitCondition chainHeadHasProgressed(
final PantheonNode node, final int blocksAheadOfLatest) {
final BigInteger futureBlock =
node.execute(eth.blockNumber()).add(BigInteger.valueOf(blocksAheadOfLatest));
return new ExpectBlockNumber(eth, futureBlock)::verify;
}
public WaitCondition cliqueValidatorsChanged(final Node node) {
return new WaitUntilSignersChanged(node.execute(clique.createGetSigners(LATEST)), clique);
}
public WaitCondition chainHeadIsAt(final long blockNumber) {
return new ExpectBlockNumber(eth, BigInteger.valueOf(blockNumber))::verify;
}
}

@ -0,0 +1,42 @@
/*
* 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.dsl.waitcondition;
import static org.assertj.core.api.Assertions.assertThat;
import static tech.pegasys.pantheon.tests.acceptance.dsl.WaitUtils.waitFor;
import static tech.pegasys.pantheon.tests.acceptance.dsl.transaction.clique.CliqueTransactions.LATEST;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.Node;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.clique.CliqueTransactions;
import java.util.List;
public class WaitUntilSignersChanged implements WaitCondition {
private final CliqueTransactions clique;
private final List<Address> initialSigners;
public WaitUntilSignersChanged(
final List<Address> initialSigners, final CliqueTransactions clique) {
this.initialSigners = initialSigners;
this.clique = clique;
}
@Override
public void waitUntil(final Node node) {
waitFor(
60,
() ->
assertThat(node.execute(clique.createGetSigners(LATEST))).isNotEqualTo(initialSigners));
}
}

@ -0,0 +1,42 @@
{
"config": {
"chainId": 4,
"homesteadBlock": 1,
"eip150Block": 2,
"eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"clique": {
"period": 5,
"epoch": 30000
}
},
"nonce": "0x0",
"timestamp": "0x58ee40ba",
"extraData": "%cliqueExtraData%",
"gasLimit": "0x47b760",
"difficulty": "0x1",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"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"
}

@ -15,11 +15,8 @@ package tech.pegasys.pantheon.consensus.clique;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import tech.pegasys.pantheon.crypto.SECP256K1.PrivateKey;
import tech.pegasys.pantheon.crypto.SECP256K1.PublicKey;
import tech.pegasys.pantheon.crypto.SECP256K1.Signature; import tech.pegasys.pantheon.crypto.SECP256K1.Signature;
import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.Util;
import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.BytesValue;
import tech.pegasys.pantheon.util.bytes.BytesValues; import tech.pegasys.pantheon.util.bytes.BytesValues;
@ -110,20 +107,10 @@ public class CliqueExtraData {
return validators; return validators;
} }
public static String createGenesisExtraDataString(final List<PrivateKey> privKeys) { public static String createGenesisExtraDataString(final List<Address> validators) {
final List<Address> validators = convertPrivKeysToAddresses(privKeys);
final CliqueExtraData cliqueExtraData = final CliqueExtraData cliqueExtraData =
new CliqueExtraData(BytesValue.wrap(new byte[32]), null, validators); new CliqueExtraData(BytesValue.wrap(new byte[32]), null, validators);
final BytesValue output = cliqueExtraData.encode(); final BytesValue output = cliqueExtraData.encode();
return output.toString(); return output.toString();
} }
private static List<Address> convertPrivKeysToAddresses(final List<PrivateKey> privKeys) {
final List<Address> validators = Lists.newArrayList();
for (final PrivateKey privKey : privKeys) {
final PublicKey pubKey = PublicKey.create(privKey);
validators.add(Util.publicKeyToAddress(pubKey));
}
return validators;
}
} }

@ -16,7 +16,6 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThatThrownBy;
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.crypto.SECP256K1.PrivateKey;
import tech.pegasys.pantheon.crypto.SECP256K1.Signature; import tech.pegasys.pantheon.crypto.SECP256K1.Signature;
import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.AddressHelpers; import tech.pegasys.pantheon.ethereum.core.AddressHelpers;
@ -95,26 +94,23 @@ public class CliqueExtraDataTest {
} }
@Test @Test
public void privKeysToExtraDataString() { public void addressToExtraDataString() {
final List<KeyPair> nodeKeys = Lists.newArrayList(); final List<KeyPair> nodeKeys = Lists.newArrayList();
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
nodeKeys.add(KeyPair.generate()); nodeKeys.add(KeyPair.generate());
} }
final List<PrivateKey> privKeys = final List<Address> addresses =
nodeKeys.stream().map(k -> k.getPrivateKey()).collect(Collectors.toList());
final String hexOutput = CliqueExtraData.createGenesisExtraDataString(privKeys);
final CliqueExtraData extraData = CliqueExtraData.decode(BytesValue.fromHexString(hexOutput));
final List<Address> expectedAddresses =
nodeKeys nodeKeys
.stream() .stream()
.map(k -> Util.publicKeyToAddress(k.getPublicKey())) .map(KeyPair::getPublicKey)
.map(Util::publicKeyToAddress)
.collect(Collectors.toList()); .collect(Collectors.toList());
assertThat(extraData.getValidators()) final String hexOutput = CliqueExtraData.createGenesisExtraDataString(addresses);
.containsExactly(expectedAddresses.toArray(new Address[expectedAddresses.size()]));
final CliqueExtraData extraData = CliqueExtraData.decode(BytesValue.fromHexString(hexOutput));
assertThat(extraData.getValidators()).containsExactly(addresses.toArray(new Address[0]));
} }
} }

@ -12,10 +12,12 @@
*/ */
package tech.pegasys.pantheon.cli; package tech.pegasys.pantheon.cli;
import static java.nio.charset.StandardCharsets.UTF_8;
import static tech.pegasys.pantheon.ethereum.p2p.config.DiscoveryConfiguration.MAINNET_BOOTSTRAP_NODES; import static tech.pegasys.pantheon.ethereum.p2p.config.DiscoveryConfiguration.MAINNET_BOOTSTRAP_NODES;
import static tech.pegasys.pantheon.ethereum.p2p.config.DiscoveryConfiguration.RINKEBY_BOOTSTRAP_NODES; import static tech.pegasys.pantheon.ethereum.p2p.config.DiscoveryConfiguration.RINKEBY_BOOTSTRAP_NODES;
import static tech.pegasys.pantheon.ethereum.p2p.config.DiscoveryConfiguration.ROPSTEN_BOOTSTRAP_NODES; import static tech.pegasys.pantheon.ethereum.p2p.config.DiscoveryConfiguration.ROPSTEN_BOOTSTRAP_NODES;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.Collection; import java.util.Collection;
@ -31,12 +33,12 @@ public class EthNetworkConfig {
private static final String MAINNET_GENESIS = "mainnet.json"; private static final String MAINNET_GENESIS = "mainnet.json";
private static final String RINKEBY_GENESIS = "rinkeby.json"; private static final String RINKEBY_GENESIS = "rinkeby.json";
private static final String ROPSTEN_GENESIS = "ropsten.json"; private static final String ROPSTEN_GENESIS = "ropsten.json";
private final URI genesisConfig; private final String genesisConfig;
private final int networkId; private final int networkId;
private final Collection<?> bootNodes; private final Collection<?> bootNodes;
public EthNetworkConfig( public EthNetworkConfig(
final URI genesisConfig, final int networkId, final Collection<?> bootNodes) { final String genesisConfig, final int networkId, final Collection<?> bootNodes) {
Preconditions.checkNotNull(genesisConfig); Preconditions.checkNotNull(genesisConfig);
Preconditions.checkNotNull(bootNodes); Preconditions.checkNotNull(bootNodes);
this.genesisConfig = genesisConfig; this.genesisConfig = genesisConfig;
@ -44,7 +46,7 @@ public class EthNetworkConfig {
this.bootNodes = bootNodes; this.bootNodes = bootNodes;
} }
public URI getGenesisConfig() { public String getGenesisConfig() {
return genesisConfig; return genesisConfig;
} }
@ -88,31 +90,32 @@ public class EthNetworkConfig {
} }
public static EthNetworkConfig mainnet() { public static EthNetworkConfig mainnet() {
final URI genesisConfig = jsonConfigURI(MAINNET_GENESIS); return new EthNetworkConfig(
return new EthNetworkConfig(genesisConfig, MAINNET_NETWORK_ID, MAINNET_BOOTSTRAP_NODES); jsonConfig(MAINNET_GENESIS), MAINNET_NETWORK_ID, MAINNET_BOOTSTRAP_NODES);
} }
public static EthNetworkConfig rinkeby() { public static EthNetworkConfig rinkeby() {
final URI genesisConfig = jsonConfigURI(RINKEBY_GENESIS); return new EthNetworkConfig(
return new EthNetworkConfig(genesisConfig, RINKEBY_NETWORK_ID, RINKEBY_BOOTSTRAP_NODES); jsonConfig(RINKEBY_GENESIS), RINKEBY_NETWORK_ID, RINKEBY_BOOTSTRAP_NODES);
} }
public static EthNetworkConfig ropsten() { public static EthNetworkConfig ropsten() {
final URI genesisConfig = jsonConfigURI(ROPSTEN_GENESIS); return new EthNetworkConfig(
return new EthNetworkConfig(genesisConfig, ROPSTEN_NETWORK_ID, ROPSTEN_BOOTSTRAP_NODES); jsonConfig(ROPSTEN_GENESIS), ROPSTEN_NETWORK_ID, ROPSTEN_BOOTSTRAP_NODES);
} }
private static URI jsonConfigURI(final String resourceName) { private static String jsonConfig(final String resourceName) {
try { try {
return Resources.getResource(resourceName).toURI(); URI uri = Resources.getResource(resourceName).toURI();
} catch (final URISyntaxException e) { return Resources.toString(uri.toURL(), UTF_8);
} catch (final URISyntaxException | IOException e) {
throw new IllegalStateException(e); throw new IllegalStateException(e);
} }
} }
public static class Builder { public static class Builder {
private URI genesisConfig; private String genesisConfig;
private int networkId; private int networkId;
private Collection<?> bootNodes; private Collection<?> bootNodes;
@ -122,7 +125,7 @@ public class EthNetworkConfig {
this.bootNodes = ethNetworkConfig.bootNodes; this.bootNodes = ethNetworkConfig.bootNodes;
} }
public Builder setGenesisConfig(final URI genesisConfig) { public Builder setGenesisConfig(final String genesisConfig) {
this.genesisConfig = genesisConfig; this.genesisConfig = genesisConfig;
return this; return this;
} }

@ -13,6 +13,7 @@
package tech.pegasys.pantheon.cli; package tech.pegasys.pantheon.cli;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static java.nio.charset.StandardCharsets.UTF_8;
import tech.pegasys.pantheon.Runner; import tech.pegasys.pantheon.Runner;
import tech.pegasys.pantheon.RunnerBuilder; import tech.pegasys.pantheon.RunnerBuilder;
@ -51,6 +52,7 @@ import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Stream; import java.util.stream.Stream;
import com.google.common.io.Resources;
import com.google.common.net.HostAndPort; import com.google.common.net.HostAndPort;
import io.vertx.core.Vertx; import io.vertx.core.Vertx;
import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Level;
@ -618,7 +620,7 @@ public class PantheonCommand implements Runnable {
private EthNetworkConfig updateNetworkConfig(final EthNetworkConfig ethNetworkConfig) { private EthNetworkConfig updateNetworkConfig(final EthNetworkConfig ethNetworkConfig) {
final EthNetworkConfig.Builder builder = new EthNetworkConfig.Builder(ethNetworkConfig); final EthNetworkConfig.Builder builder = new EthNetworkConfig.Builder(ethNetworkConfig);
if (genesisFile != null) { if (genesisFile != null) {
builder.setGenesisConfig(genesisFile.toPath().toUri()); builder.setGenesisConfig(genesisConfig());
} }
if (networkId != null) { if (networkId != null) {
builder.setNetworkId(networkId); builder.setNetworkId(networkId);
@ -628,4 +630,13 @@ public class PantheonCommand implements Runnable {
} }
return builder.build(); return builder.build();
} }
private String genesisConfig() {
try {
return Resources.toString(genesisFile.toURI().toURL(), UTF_8);
} catch (IOException e) {
throw new ParameterException(
new CommandLine(this), String.format("Unable to load genesis file %s.", genesisFile), e);
}
}
} }

@ -12,7 +12,6 @@
*/ */
package tech.pegasys.pantheon.cli; package tech.pegasys.pantheon.cli;
import static java.nio.charset.StandardCharsets.UTF_8;
import static tech.pegasys.pantheon.controller.KeyPairUtil.loadKeyPair; import static tech.pegasys.pantheon.controller.KeyPairUtil.loadKeyPair;
import static tech.pegasys.pantheon.controller.PantheonController.DATABASE_PATH; import static tech.pegasys.pantheon.controller.PantheonController.DATABASE_PATH;
@ -30,8 +29,6 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import com.google.common.io.Resources;
public class PantheonControllerBuilder { public class PantheonControllerBuilder {
public PantheonController<?> build( public PantheonController<?> build(
@ -59,8 +56,7 @@ public class PantheonControllerBuilder {
miningParameters, miningParameters,
nodeKeys); nodeKeys);
} else { } else {
final String genesisConfig = final String genesisConfig = ethNetworkConfig.getGenesisConfig();
Resources.toString(ethNetworkConfig.getGenesisConfig().toURL(), UTF_8);
final GenesisConfigFile genesisConfigFile = GenesisConfigFile.fromConfig(genesisConfig); final GenesisConfigFile genesisConfigFile = GenesisConfigFile.fromConfig(genesisConfig);
return PantheonController.fromConfig( return PantheonController.fromConfig(
genesisConfigFile, genesisConfigFile,

@ -42,6 +42,7 @@ import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.Writer; import java.io.Writer;
import java.net.URL;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
@ -61,10 +62,10 @@ import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers; import org.mockito.ArgumentMatchers;
public class PantheonCommandTest extends CommandTestAbstract { public class PantheonCommandTest extends CommandTestAbstract {
@Rule public final TemporaryFolder temp = new TemporaryFolder(); @Rule public final TemporaryFolder temp = new TemporaryFolder();
private static final JsonRpcConfiguration defaultJsonRpcConfiguration; private static final JsonRpcConfiguration defaultJsonRpcConfiguration;
private static final WebSocketConfiguration defaultWebSocketConfiguration; private static final WebSocketConfiguration defaultWebSocketConfiguration;
private static final String GENESIS_CONFIG_TESTDATA = "genesis_config";
static { static {
final JsonRpcConfiguration rpcConf = JsonRpcConfiguration.createDefault(); final JsonRpcConfiguration rpcConf = JsonRpcConfiguration.createDefault();
@ -160,13 +161,12 @@ public class PantheonCommandTest extends CommandTestAbstract {
assertThat(miningArg.getValue().getMinTransactionGasPrice()).isEqualTo(Wei.of(1000)); assertThat(miningArg.getValue().getMinTransactionGasPrice()).isEqualTo(Wei.of(1000));
assertThat(miningArg.getValue().getExtraData()).isEqualTo(BytesValue.EMPTY); assertThat(miningArg.getValue().getExtraData()).isEqualTo(BytesValue.EMPTY);
assertThat(networkArg.getValue().getNetworkId()).isEqualTo(1); assertThat(networkArg.getValue().getNetworkId()).isEqualTo(1);
assertThat(networkArg.getValue().getGenesisConfig().toString()).endsWith("mainnet.json");
assertThat(networkArg.getValue().getBootNodes()).isEqualTo(MAINNET_BOOTSTRAP_NODES); assertThat(networkArg.getValue().getBootNodes()).isEqualTo(MAINNET_BOOTSTRAP_NODES);
} }
// Testing each option // Testing each option
@Test @Test
public void CallingWithConfigOptionButNoConfigFileShouldDisplayHelp() { public void callingWithConfigOptionButNoConfigFileShouldDisplayHelp() {
parseCommand("--config"); parseCommand("--config");
@ -176,7 +176,7 @@ public class PantheonCommandTest extends CommandTestAbstract {
} }
@Test @Test
public void CallingWithConfigOptionButNonExistingFileShouldDisplayHelp() throws IOException { public void callingWithConfigOptionButNonExistingFileShouldDisplayHelp() throws IOException {
final File tempConfigFile = temp.newFile("an-invalid-file-name-without-extension"); final File tempConfigFile = temp.newFile("an-invalid-file-name-without-extension");
parseCommand("--config", tempConfigFile.getPath()); parseCommand("--config", tempConfigFile.getPath());
@ -186,7 +186,7 @@ public class PantheonCommandTest extends CommandTestAbstract {
} }
@Test @Test
public void CallingWithConfigOptionButTomlFileNotFoundShouldDisplayHelp() { public void callingWithConfigOptionButTomlFileNotFoundShouldDisplayHelp() {
parseCommand("--config", "./an-invalid-file-name-sdsd87sjhqoi34io23.toml"); parseCommand("--config", "./an-invalid-file-name-sdsd87sjhqoi34io23.toml");
@ -196,7 +196,7 @@ public class PantheonCommandTest extends CommandTestAbstract {
} }
@Test @Test
public void CallingWithConfigOptionButInvalidContentTomlFileShouldDisplayHelp() throws Exception { public void callingWithConfigOptionButInvalidContentTomlFileShouldDisplayHelp() throws Exception {
// We write a config file to prevent an invalid file in resource folder to raise errors in // We write a config file to prevent an invalid file in resource folder to raise errors in
// code checks (CI + IDE) // code checks (CI + IDE)
@ -217,7 +217,7 @@ public class PantheonCommandTest extends CommandTestAbstract {
} }
@Test @Test
public void CallingWithConfigOptionButInvalidValueTomlFileShouldDisplayHelp() throws Exception { public void callingWithConfigOptionButInvalidValueTomlFileShouldDisplayHelp() throws Exception {
// We write a config file to prevent an invalid file in resource folder to raise errors in // We write a config file to prevent an invalid file in resource folder to raise errors in
// code checks (CI + IDE) // code checks (CI + IDE)
@ -238,8 +238,13 @@ public class PantheonCommandTest extends CommandTestAbstract {
} }
@Test @Test
public void OverrideDefaultValuesIfKeyIsPresentInConfigFile() throws IOException { public void overrideDefaultValuesIfKeyIsPresentInConfigFile() throws IOException {
final String configFile = Resources.getResource("complete_config.toml").getFile(); final URL configFile = Resources.getResource("complete_config.toml");
final Path genesisFile = createFakeGenesisFile();
final String updatedConfig =
Resources.toString(configFile, UTF_8).replaceAll("~/genesis.json", genesisFile.toString());
final Path toml = Files.createTempFile("toml", "");
Files.write(toml, updatedConfig.getBytes(UTF_8));
final JsonRpcConfiguration jsonRpcConfiguration = JsonRpcConfiguration.createDefault(); final JsonRpcConfiguration jsonRpcConfiguration = JsonRpcConfiguration.createDefault();
jsonRpcConfiguration.setEnabled(false); jsonRpcConfiguration.setEnabled(false);
@ -258,7 +263,7 @@ public class PantheonCommandTest extends CommandTestAbstract {
webSocketConfiguration.addRpcApi(CliqueRpcApis.CLIQUE); webSocketConfiguration.addRpcApi(CliqueRpcApis.CLIQUE);
webSocketConfiguration.addRpcApi(IbftRpcApis.IBFT); webSocketConfiguration.addRpcApi(IbftRpcApis.IBFT);
parseCommand("--config", configFile); parseCommand("--config", toml.toString());
verify(mockRunnerBuilder) verify(mockRunnerBuilder)
.build( .build(
@ -280,7 +285,7 @@ public class PantheonCommandTest extends CommandTestAbstract {
final EthNetworkConfig networkConfig = final EthNetworkConfig networkConfig =
new Builder(EthNetworkConfig.mainnet()) new Builder(EthNetworkConfig.mainnet())
.setGenesisConfig(new File("~/genesys.json").toPath().toUri()) .setGenesisConfig(GENESIS_CONFIG_TESTDATA)
.setBootNodes(nodes) .setBootNodes(nodes)
.build(); .build();
verify(mockControllerBuilder) verify(mockControllerBuilder)
@ -303,7 +308,7 @@ public class PantheonCommandTest extends CommandTestAbstract {
} }
@Test @Test
public void NoOverrideDefaultValuesIfKeyIsNotPresentInConfigFile() throws IOException { public void noOverrideDefaultValuesIfKeyIsNotPresentInConfigFile() throws IOException {
final String configFile = Resources.getResource("partial_config.toml").getFile(); final String configFile = Resources.getResource("partial_config.toml").getFile();
parseCommand("--config", configFile); parseCommand("--config", configFile);
@ -386,16 +391,16 @@ public class PantheonCommandTest extends CommandTestAbstract {
@Test @Test
public void genesisPathOptionMustBeUsed() throws Exception { public void genesisPathOptionMustBeUsed() throws Exception {
final Path path = Paths.get("."); final Path genesisFile = createFakeGenesisFile();
final ArgumentCaptor<EthNetworkConfig> networkArg = final ArgumentCaptor<EthNetworkConfig> networkArg =
ArgumentCaptor.forClass(EthNetworkConfig.class); ArgumentCaptor.forClass(EthNetworkConfig.class);
parseCommand("--genesis", path.toString()); parseCommand("--genesis", genesisFile.toString());
verify(mockControllerBuilder) verify(mockControllerBuilder)
.build(any(), any(), networkArg.capture(), anyBoolean(), any(), anyBoolean(), any()); .build(any(), any(), networkArg.capture(), anyBoolean(), any(), anyBoolean(), any());
assertThat(networkArg.getValue().getGenesisConfig()).isEqualTo(path.toUri()); assertThat(networkArg.getValue().getGenesisConfig()).isEqualTo("genesis_config");
assertThat(commandOutput.toString()).isEmpty(); assertThat(commandOutput.toString()).isEmpty();
assertThat(commandErrorOutput.toString()).isEmpty(); assertThat(commandErrorOutput.toString()).isEmpty();
@ -996,7 +1001,7 @@ public class PantheonCommandTest extends CommandTestAbstract {
@Test @Test
public void rinkebyValuesCanBeOverridden() throws Exception { public void rinkebyValuesCanBeOverridden() throws Exception {
final String[] nodes = {"enode://001@123:4567", "enode://002@123:4567", "enode://003@123:4567"}; final String[] nodes = {"enode://001@123:4567", "enode://002@123:4567", "enode://003@123:4567"};
final Path path = Paths.get("."); final Path genesisFile = createFakeGenesisFile();
parseCommand( parseCommand(
"--rinkeby", "--rinkeby",
"--network-id", "--network-id",
@ -1004,7 +1009,7 @@ public class PantheonCommandTest extends CommandTestAbstract {
"--bootnodes", "--bootnodes",
String.join(",", nodes), String.join(",", nodes),
"--genesis", "--genesis",
path.toString()); genesisFile.toString());
final ArgumentCaptor<EthNetworkConfig> networkArg = final ArgumentCaptor<EthNetworkConfig> networkArg =
ArgumentCaptor.forClass(EthNetworkConfig.class); ArgumentCaptor.forClass(EthNetworkConfig.class);
@ -1012,8 +1017,14 @@ public class PantheonCommandTest extends CommandTestAbstract {
.build(any(), any(), networkArg.capture(), anyBoolean(), any(), anyBoolean(), any()); .build(any(), any(), networkArg.capture(), anyBoolean(), any(), anyBoolean(), any());
assertThat(commandOutput.toString()).isEmpty(); assertThat(commandOutput.toString()).isEmpty();
assertThat(commandErrorOutput.toString()).isEmpty(); assertThat(commandErrorOutput.toString()).isEmpty();
assertThat(networkArg.getValue().getGenesisConfig()).isEqualTo(path.toUri()); assertThat(networkArg.getValue().getGenesisConfig()).isEqualTo("genesis_config");
assertThat(networkArg.getValue().getBootNodes()).isEqualTo(Arrays.asList(nodes)); assertThat(networkArg.getValue().getBootNodes()).isEqualTo(Arrays.asList(nodes));
assertThat(networkArg.getValue().getNetworkId()).isEqualTo(1); assertThat(networkArg.getValue().getNetworkId()).isEqualTo(1);
} }
private Path createFakeGenesisFile() throws IOException {
final Path genesisFile = Files.createTempFile("genesisFile", "");
Files.write(genesisFile, "genesis_config".getBytes(UTF_8));
return genesisFile;
}
} }

@ -13,7 +13,7 @@ rpc-listen="5.6.7.8:5678" # IP:port
ws-listen="9.10.11.12:9101" # IP:port ws-listen="9.10.11.12:9101" # IP:port
# chain # chain
genesis="~/genesys.json" # Path genesis="~/genesis.json" # Path
sync-mode="fast"# should be FAST or FULL (or fast or full) sync-mode="fast"# should be FAST or FULL (or fast or full)
ottoman=false # true means using ottoman testnet if genesys file uses iBFT ottoman=false # true means using ottoman testnet if genesys file uses iBFT

Loading…
Cancel
Save