Acc whitelist api (#487)

* Implemented list/add/remove accounts from whitelist

* Including account whitelist methods in the JSON-RPC API

* Fixing json rpc response for eth_sendrawTransaction with account not authorized

* Refactoring TransactionPool account whitelist logic

* Acceptance test for accounts whitelist

* Errorprone

* Fixed account nonce tracking in ATs

Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>
pull/2/head
Lucas Saldanha 6 years ago committed by GitHub
parent 876214024f
commit 4025a06db0
  1. 3
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/AcceptanceTestBase.java
  2. 8
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/account/Account.java
  3. 41
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/eth/ExpectEthSendRawTransactionException.java
  4. 35
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/perm/AddAccountsToWhitelistSuccessfully.java
  5. 39
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/perm/GetExpectedAccountsWhitelist.java
  6. 35
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/condition/perm/RemoveAccountsFromWhitelistSuccessfully.java
  7. 7
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Eth.java
  8. 44
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/jsonrpc/Perm.java
  9. 4
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ProcessPantheonNodeRunner.java
  10. 24
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonNodeFactory.java
  11. 30
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/PantheonWeb3j.java
  12. 28
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/Transactions.java
  13. 38
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/account/TransferTransaction.java
  14. 45
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eth/EthGetTransactionCountTransaction.java
  15. 42
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eth/EthSendRawTransactionTransaction.java
  16. 5
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/eth/EthTransactions.java
  17. 42
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/perm/PermAddAccountsToWhitelistTransaction.java
  18. 36
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/perm/PermGetAccountsWhitelistTransaction.java
  19. 43
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/transaction/perm/PermRemoveAccountsFromWhitelistTransaction.java
  20. 79
      acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/permissioning/AccountsWhitelistAcceptanceTest.java
  21. 14
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/TransactionPool.java
  22. 1
      ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/core/TransactionPoolTest.java
  23. 1
      ethereum/jsonrpc/build.gradle
  24. 4
      ethereum/jsonrpc/src/integration-test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcTestMethodsFactory.java
  25. 2
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcErrorConverter.java
  26. 16
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java
  27. 3
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/RpcApis.java
  28. 62
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/permissioning/PermAddAccountsToWhitelist.java
  29. 44
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/permissioning/PermGetAccountsWhitelist.java
  30. 62
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/permissioning/PermRemoveAccountsFromWhitelist.java
  31. 9
      ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java
  32. 6
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/AbstractEthJsonRpcHttpServiceTest.java
  33. 2
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceHostWhitelistTest.java
  34. 2
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceRpcApisTest.java
  35. 2
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceTest.java
  36. 6
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/EthSendRawTransactionTest.java
  37. 105
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/permissioning/PermAddAccountsToWhitelistTest.java
  38. 88
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/permissioning/PermGetAccountsWhitelistTest.java
  39. 105
      ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/permissioning/PermRemoveAccountsFromWhitelistTest.java
  40. 1
      ethereum/permissioning/build.gradle
  41. 79
      ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/AccountWhitelistController.java
  42. 142
      ethereum/permissioning/src/test/java/tech/pegasys/pantheon/ethereum/permissioning/AccountWhitelistControllerTest.java
  43. 16
      pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java

@ -18,6 +18,7 @@ import tech.pegasys.pantheon.tests.acceptance.dsl.condition.clique.CliqueConditi
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;
import tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc.Perm;
import tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc.Web3; import tech.pegasys.pantheon.tests.acceptance.dsl.jsonrpc.Web3;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.cluster.Cluster; import tech.pegasys.pantheon.tests.acceptance.dsl.node.cluster.Cluster;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.factory.PantheonNodeFactory; import tech.pegasys.pantheon.tests.acceptance.dsl.node.factory.PantheonNodeFactory;
@ -41,6 +42,7 @@ public class AcceptanceTestBase {
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 Perm perm;
protected final PantheonNodeFactory pantheon; protected final PantheonNodeFactory pantheon;
protected final ContractVerifier contractVerifier; protected final ContractVerifier contractVerifier;
protected final WaitConditions wait; protected final WaitConditions wait;
@ -55,6 +57,7 @@ public class AcceptanceTestBase {
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);
perm = new Perm(transactions);
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());

@ -54,14 +54,14 @@ public class Account {
keyPair.getPrivateKey().toString(), keyPair.getPublicKey().toString()); keyPair.getPrivateKey().toString(), keyPair.getPublicKey().toString());
} }
public String getAddress() {
return Address.extract(Hash.hash(keyPair.getPublicKey().getEncodedBytes())).toString();
}
public BigInteger getNextNonce() { public BigInteger getNextNonce() {
return BigInteger.valueOf(nonce++); return BigInteger.valueOf(nonce++);
} }
public String getAddress() {
return Address.extract(Hash.hash(keyPair.getPublicKey().getEncodedBytes())).toString();
}
public Condition balanceEquals(final String expectedBalance, final Unit balanceUnit) { public Condition balanceEquals(final String expectedBalance, final Unit balanceUnit) {
return new ExpectAccountBalance(eth, this, expectedBalance, balanceUnit); return new ExpectAccountBalance(eth, this, expectedBalance, balanceUnit);
} }

@ -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.condition.eth;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
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.EthSendRawTransactionTransaction;
import org.web3j.protocol.exceptions.ClientConnectionException;
public class ExpectEthSendRawTransactionException implements Condition {
private final EthSendRawTransactionTransaction transaction;
private final String expectedMessage;
public ExpectEthSendRawTransactionException(
final EthSendRawTransactionTransaction transaction, final String expectedMessage) {
this.transaction = transaction;
this.expectedMessage = expectedMessage;
}
@Override
public void verify(final Node node) {
final Throwable thrown = catchThrowable(() -> node.execute(transaction));
assertThat(thrown).isInstanceOf(ClientConnectionException.class);
assertThat(thrown.getMessage()).contains(expectedMessage);
}
}

@ -0,0 +1,35 @@
/*
* 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.perm;
import static org.assertj.core.api.Assertions.assertThat;
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.perm.PermAddAccountsToWhitelistTransaction;
public class AddAccountsToWhitelistSuccessfully implements Condition {
private final PermAddAccountsToWhitelistTransaction transaction;
public AddAccountsToWhitelistSuccessfully(
final PermAddAccountsToWhitelistTransaction transaction) {
this.transaction = transaction;
}
@Override
public void verify(final Node node) {
Boolean result = node.execute(transaction);
assertThat(result).isTrue();
}
}

@ -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.perm;
import static org.assertj.core.api.Assertions.assertThat;
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.perm.PermGetAccountsWhitelistTransaction;
import java.util.List;
public class GetExpectedAccountsWhitelist implements Condition {
private final PermGetAccountsWhitelistTransaction transaction;
private final List<String> expectedList;
public GetExpectedAccountsWhitelist(
final PermGetAccountsWhitelistTransaction transaction, final List<String> expectedList) {
this.transaction = transaction;
this.expectedList = expectedList;
}
@Override
public void verify(final Node node) {
List<String> accounts = node.execute(transaction);
assertThat(accounts).containsExactlyInAnyOrder(expectedList.toArray(new String[] {}));
}
}

@ -0,0 +1,35 @@
/*
* 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.perm;
import static org.assertj.core.api.Assertions.assertThat;
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.perm.PermRemoveAccountsFromWhitelistTransaction;
public class RemoveAccountsFromWhitelistSuccessfully implements Condition {
private final PermRemoveAccountsFromWhitelistTransaction transaction;
public RemoveAccountsFromWhitelistSuccessfully(
final PermRemoveAccountsFromWhitelistTransaction transaction) {
this.transaction = transaction;
}
@Override
public void verify(final Node node) {
Boolean result = node.execute(transaction);
assertThat(result).isTrue();
}
}

@ -17,6 +17,7 @@ import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.eth.ExpectEthAccountsException; import tech.pegasys.pantheon.tests.acceptance.dsl.condition.eth.ExpectEthAccountsException;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.eth.ExpectEthGetTransactionReceiptIsAbsent; import tech.pegasys.pantheon.tests.acceptance.dsl.condition.eth.ExpectEthGetTransactionReceiptIsAbsent;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.eth.ExpectEthGetWorkException; import tech.pegasys.pantheon.tests.acceptance.dsl.condition.eth.ExpectEthGetWorkException;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.eth.ExpectEthSendRawTransactionException;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.eth.ExpectSuccessfulEthGetTransactionReceipt; import tech.pegasys.pantheon.tests.acceptance.dsl.condition.eth.ExpectSuccessfulEthGetTransactionReceipt;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.eth.SanityCheckEthGetWorkValues; import tech.pegasys.pantheon.tests.acceptance.dsl.condition.eth.SanityCheckEthGetWorkValues;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eth.EthTransactions; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eth.EthTransactions;
@ -50,4 +51,10 @@ public class Eth {
return new ExpectEthGetTransactionReceiptIsAbsent( return new ExpectEthGetTransactionReceiptIsAbsent(
transactions.getTransactionReceipt(transactionHash)); transactions.getTransactionReceipt(transactionHash));
} }
public Condition sendRawTransactionExceptional(
final String transactionData, final String expectedMessage) {
return new ExpectEthSendRawTransactionException(
transactions.sendRawTransactionTransaction(transactionData), expectedMessage);
}
} }

@ -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.jsonrpc;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.Condition;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.perm.AddAccountsToWhitelistSuccessfully;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.perm.GetExpectedAccountsWhitelist;
import tech.pegasys.pantheon.tests.acceptance.dsl.condition.perm.RemoveAccountsFromWhitelistSuccessfully;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transactions;
import java.util.Arrays;
public class Perm {
public Perm(final Transactions transactions) {
this.transactions = transactions;
}
private final Transactions transactions;
public Condition addAccountsToWhitelist(final String... accounts) {
return new AddAccountsToWhitelistSuccessfully(transactions.addAccountsToWhitelist(accounts));
}
public Condition removeAccountsFromWhitelist(final String... accounts) {
return new RemoveAccountsFromWhitelistSuccessfully(
transactions.removeAccountsFromWhitelist(accounts));
}
public Condition expectAccountsWhitelist(final String... expectedAccounts) {
return new GetExpectedAccountsWhitelist(
transactions.getAccountsWhiteList(), Arrays.asList(expectedAccounts));
}
}

@ -76,6 +76,10 @@ public class ProcessPantheonNodeRunner implements PantheonNodeRunner {
params.add("--nodes-whitelist"); params.add("--nodes-whitelist");
params.add(String.join(",", permissioningConfiguration.getNodeWhitelist().toString())); params.add(String.join(",", permissioningConfiguration.getNodeWhitelist().toString()));
} }
if (permissioningConfiguration.isAccountWhitelistSet()) {
params.add("--accounts-whitelist");
params.add(String.join(",", permissioningConfiguration.getAccountWhitelist().toString()));
}
if (node.jsonRpcEnabled()) { if (node.jsonRpcEnabled()) {
params.add("--rpc-enabled"); params.add("--rpc-enabled");

@ -21,6 +21,7 @@ import tech.pegasys.pantheon.consensus.clique.CliqueExtraData;
import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration; import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration;
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.websocket.WebSocketConfiguration; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration;
import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration;
import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNode; import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNode;
@ -138,6 +139,21 @@ public class PantheonNodeFactory {
.build()); .build());
} }
public PantheonNode createNodeWithAccountsWhitelist(
final String name, final List<String> accountsWhitelist) throws IOException {
PermissioningConfiguration permissioningConfiguration =
PermissioningConfiguration.createDefault();
permissioningConfiguration.setAccountWhitelist(accountsWhitelist);
return create(
new PantheonFactoryConfigurationBuilder()
.setName(name)
.miningEnabled()
.setJsonRpcConfiguration(jsonRpcConfigWithPermissioning())
.setPermissioningConfiguration(permissioningConfiguration)
.build());
}
public PantheonNode createCliqueNode(final String name) throws IOException { public PantheonNode createCliqueNode(final String name) throws IOException {
return create( return create(
new PantheonFactoryConfigurationBuilder() new PantheonFactoryConfigurationBuilder()
@ -215,4 +231,12 @@ public class PantheonNodeFactory {
config.setPort(0); config.setPort(0);
return config; return config;
} }
private JsonRpcConfiguration jsonRpcConfigWithPermissioning() {
final JsonRpcConfiguration jsonRpcConfig = createJsonRpcEnabledConfig();
final List<RpcApi> rpcApis = new ArrayList<>(jsonRpcConfig.getRpcApis());
rpcApis.add(RpcApis.PERM);
jsonRpcConfig.setRpcApis(rpcApis);
return jsonRpcConfig;
}
} }

@ -19,6 +19,7 @@ import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.Hash; import tech.pegasys.pantheon.ethereum.core.Hash;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
@ -78,4 +79,33 @@ public class PantheonWeb3j extends JsonRpc2_0Web3j {
public static class SignersBlockResponse extends Response<List<Address>> {} public static class SignersBlockResponse extends Response<List<Address>> {}
public static class ProposalsResponse extends Response<Map<Address, Boolean>> {} public static class ProposalsResponse extends Response<Map<Address, Boolean>> {}
public Request<?, AddAccountsToWhitelistResponse> addAccountsToWhitelist(
final List<String> accounts) {
return new Request<>(
"perm_addAccountsToWhitelist",
Collections.singletonList(accounts),
web3jService,
AddAccountsToWhitelistResponse.class);
}
public Request<?, RemoveAccountsFromWhitelistResponse> removeAccountsFromWhitelist(
final List<String> accounts) {
return new Request<>(
"perm_removeAccountsFromWhitelist",
Collections.singletonList(accounts),
web3jService,
RemoveAccountsFromWhitelistResponse.class);
}
public Request<?, GetAccountsWhitelistResponse> getAccountsWhitelist() {
return new Request<>(
"perm_getAccountsWhitelist", null, web3jService, GetAccountsWhitelistResponse.class);
}
public static class AddAccountsToWhitelistResponse extends Response<Boolean> {}
public static class RemoveAccountsFromWhitelistResponse extends Response<Boolean> {}
public static class GetAccountsWhitelistResponse extends Response<List<String>> {}
} }

@ -16,8 +16,14 @@ import tech.pegasys.pantheon.tests.acceptance.dsl.account.Account;
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.transaction.account.TransferTransaction; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.account.TransferTransaction;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.account.TransferTransactionSet; import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.account.TransferTransactionSet;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.eth.EthGetTransactionCountTransaction;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.perm.PermAddAccountsToWhitelistTransaction;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.perm.PermGetAccountsWhitelistTransaction;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.perm.PermRemoveAccountsFromWhitelistTransaction;
import java.math.BigInteger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import org.web3j.tx.Contract; import org.web3j.tx.Contract;
@ -40,6 +46,11 @@ public class Transactions {
return new TransferTransaction(sender, recipient, String.valueOf(amount), Unit.ETHER); return new TransferTransaction(sender, recipient, String.valueOf(amount), Unit.ETHER);
} }
public TransferTransaction createTransfer(
final Account sender, final Account recipient, final int amount, final BigInteger nonce) {
return new TransferTransaction(sender, recipient, String.valueOf(amount), Unit.ETHER, nonce);
}
public TransferTransactionSet createIncrementalTransfers( public TransferTransactionSet createIncrementalTransfers(
final Account sender, final Account recipient, final int etherAmount) { final Account sender, final Account recipient, final int etherAmount) {
final List<TransferTransaction> transfers = new ArrayList<>(); final List<TransferTransaction> transfers = new ArrayList<>();
@ -55,4 +66,21 @@ public class Transactions {
final Class<T> clazz) { final Class<T> clazz) {
return new DeploySmartContractTransaction<>(clazz); return new DeploySmartContractTransaction<>(clazz);
} }
public PermAddAccountsToWhitelistTransaction addAccountsToWhitelist(final String... accounts) {
return new PermAddAccountsToWhitelistTransaction(Arrays.asList(accounts));
}
public PermRemoveAccountsFromWhitelistTransaction removeAccountsFromWhitelist(
final String... accounts) {
return new PermRemoveAccountsFromWhitelistTransaction(Arrays.asList(accounts));
}
public PermGetAccountsWhitelistTransaction getAccountsWhiteList() {
return new PermGetAccountsWhitelistTransaction();
}
public EthGetTransactionCountTransaction getTransactionCount(final String accountAddress) {
return new EthGetTransactionCountTransaction(accountAddress);
}
} }

@ -21,6 +21,7 @@ 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 java.util.Optional;
import org.web3j.crypto.RawTransaction; import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.TransactionEncoder; import org.web3j.crypto.TransactionEncoder;
@ -36,26 +37,33 @@ public class TransferTransaction implements Transaction<Hash> {
private final Account recipient; private final Account recipient;
private final String amount; private final String amount;
private final Unit unit; private final Unit unit;
private final Optional<BigInteger> nonce;
public TransferTransaction( public TransferTransaction(
final Account sender, final Account recipient, final String amount, final Unit unit) { final Account sender, final Account recipient, final String amount, final Unit unit) {
this(sender, recipient, amount, unit, null);
}
public TransferTransaction(
final Account sender,
final Account recipient,
final String amount,
final Unit unit,
final BigInteger nonce) {
this.sender = sender; this.sender = sender;
this.recipient = recipient; this.recipient = recipient;
this.amount = amount; this.amount = amount;
this.unit = unit; this.unit = unit;
if (nonce != null) {
this.nonce = Optional.of(nonce);
} else {
this.nonce = Optional.empty();
}
} }
@Override @Override
public Hash execute(final PantheonWeb3j node) { public Hash execute(final PantheonWeb3j node) {
final RawTransaction transaction = final String signedTransactionData = signedTransactionData();
RawTransaction.createEtherTransaction(
sender.getNextNonce(),
MINIMUM_GAS_PRICE,
TRANSFER_GAS_COST,
recipient.getAddress(),
Convert.toWei(amount, unit).toBigIntegerExact());
final String signedTransactionData =
toHexString(TransactionEncoder.signMessage(transaction, sender.web3jCredentials()));
try { try {
return Hash.fromHexString( return Hash.fromHexString(
node.ethSendRawTransaction(signedTransactionData).send().getTransactionHash()); node.ethSendRawTransaction(signedTransactionData).send().getTransactionHash());
@ -63,4 +71,16 @@ public class TransferTransaction implements Transaction<Hash> {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
public String signedTransactionData() {
final RawTransaction transaction =
RawTransaction.createEtherTransaction(
nonce.orElse(nonce.orElseGet(sender::getNextNonce)),
MINIMUM_GAS_PRICE,
TRANSFER_GAS_COST,
recipient.getAddress(),
Convert.toWei(amount, unit).toBigIntegerExact());
return toHexString(TransactionEncoder.signMessage(transaction, sender.web3jCredentials()));
}
} }

@ -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.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 java.math.BigInteger;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.methods.response.EthGetTransactionCount;
public class EthGetTransactionCountTransaction implements Transaction<BigInteger> {
private final String accountAddress;
public EthGetTransactionCountTransaction(final String accountAddress) {
this.accountAddress = accountAddress;
}
@Override
public BigInteger execute(final PantheonWeb3j node) {
try {
EthGetTransactionCount result =
node.ethGetTransactionCount(accountAddress, DefaultBlockParameterName.LATEST).send();
assertThat(result).isNotNull();
return result.getTransactionCount();
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
}

@ -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.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.methods.response.EthSendTransaction;
public class EthSendRawTransactionTransaction implements Transaction<String> {
private final String transactionData;
EthSendRawTransactionTransaction(final String transactionData) {
this.transactionData = transactionData;
}
@Override
public String execute(final PantheonWeb3j node) {
try {
EthSendTransaction response = node.ethSendRawTransaction(transactionData).send();
assertThat(response.getTransactionHash()).isNotNull();
return response.getTransactionHash();
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
}

@ -46,4 +46,9 @@ public class EthTransactions {
public EthGetTransactionReceiptTransaction getTransactionReceipt(final String transactionHash) { public EthGetTransactionReceiptTransaction getTransactionReceipt(final String transactionHash) {
return new EthGetTransactionReceiptTransaction(transactionHash); return new EthGetTransactionReceiptTransaction(transactionHash);
} }
public EthSendRawTransactionTransaction sendRawTransactionTransaction(
final String transactionData) {
return new EthSendRawTransactionTransaction(transactionData);
}
} }

@ -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.transaction.perm;
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.AddAccountsToWhitelistResponse;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction;
import java.io.IOException;
import java.util.List;
public class PermAddAccountsToWhitelistTransaction implements Transaction<Boolean> {
private final List<String> accounts;
public PermAddAccountsToWhitelistTransaction(final List<String> accounts) {
this.accounts = accounts;
}
@Override
public Boolean execute(final PantheonWeb3j node) {
try {
AddAccountsToWhitelistResponse response = node.addAccountsToWhitelist(accounts).send();
assertThat(response.getResult()).isTrue();
return response.getResult();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

@ -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.transaction.perm;
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.GetAccountsWhitelistResponse;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction;
import java.io.IOException;
import java.util.List;
public class PermGetAccountsWhitelistTransaction implements Transaction<List<String>> {
@Override
public List<String> execute(final PantheonWeb3j node) {
try {
GetAccountsWhitelistResponse response = node.getAccountsWhitelist().send();
assertThat(response.getResult()).isNotNull();
return response.getResult();
} catch (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.perm;
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.RemoveAccountsFromWhitelistResponse;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.Transaction;
import java.io.IOException;
import java.util.List;
public class PermRemoveAccountsFromWhitelistTransaction implements Transaction<Boolean> {
private final List<String> accounts;
public PermRemoveAccountsFromWhitelistTransaction(final List<String> accounts) {
this.accounts = accounts;
}
@Override
public Boolean execute(final PantheonWeb3j node) {
try {
RemoveAccountsFromWhitelistResponse response =
node.removeAccountsFromWhitelist(accounts).send();
assertThat(response.getResult()).isTrue();
return response.getResult();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

@ -0,0 +1,79 @@
/*
* 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.permissioning;
import static org.web3j.utils.Convert.Unit.ETHER;
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.Node;
import tech.pegasys.pantheon.tests.acceptance.dsl.transaction.account.TransferTransaction;
import java.math.BigInteger;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
public class AccountsWhitelistAcceptanceTest extends AcceptanceTestBase {
private Node node;
private Account senderA;
private Account senderB;
@Before
public void setUp() throws Exception {
senderA = accounts.getPrimaryBenefactor();
senderB = accounts.getSecondaryBenefactor();
node = pantheon.createNodeWithAccountsWhitelist("node", Arrays.asList(senderA.getAddress()));
cluster.start(node);
}
@Test
public void onlyAllowedAccountCanSubmitTransactions() {
Account beneficiary = accounts.createAccount("beneficiary");
node.execute(transactions.createTransfer(senderA, beneficiary, 1));
node.verify(beneficiary.balanceEquals("1", ETHER));
verifyTransferForbidden(senderB, beneficiary);
}
@Test
public void manipulatingAccountsWhitelistViaJsonRpc() {
Account beneficiary = accounts.createAccount("beneficiary");
node.verify(beneficiary.balanceEquals("0", ETHER));
verifyTransferForbidden(senderB, beneficiary);
node.execute(transactions.addAccountsToWhitelist(senderB.getAddress()));
node.verify(perm.expectAccountsWhitelist(senderA.getAddress(), senderB.getAddress()));
node.execute(transactions.createTransfer(senderB, beneficiary, 1));
node.verify(beneficiary.balanceEquals("1", ETHER));
node.execute(transactions.removeAccountsFromWhitelist(senderB.getAddress()));
node.verify(perm.expectAccountsWhitelist(senderA.getAddress()));
verifyTransferForbidden(senderB, beneficiary);
}
private void verifyTransferForbidden(final Account sender, final Account beneficiary) {
BigInteger nonce = node.execute(transactions.getTransactionCount(sender.getAddress()));
TransferTransaction transfer = transactions.createTransfer(sender, beneficiary, 1, nonce);
node.verify(
eth.sendRawTransactionExceptional(
transfer.signedTransactionData(),
"Sender account not authorized to send transactions"));
}
}

@ -134,7 +134,7 @@ public class TransactionPool implements BlockAddedObserver {
return basicValidationResult; return basicValidationResult;
} }
String sender = transaction.getSender().toString(); final String sender = transaction.getSender().toString();
if (accountIsNotWhitelisted(sender)) { if (accountIsNotWhitelisted(sender)) {
return ValidationResult.invalid( return ValidationResult.invalid(
TransactionInvalidReason.TX_SENDER_NOT_AUTHORIZED, TransactionInvalidReason.TX_SENDER_NOT_AUTHORIZED,
@ -158,17 +158,7 @@ public class TransactionPool implements BlockAddedObserver {
} }
private boolean accountIsNotWhitelisted(final String account) { private boolean accountIsNotWhitelisted(final String account) {
if (useWhitelist()) { return accountWhitelistController.map(c -> !c.contains(account)).orElse(false);
if (!accountWhitelistController.get().contains(account)) {
return true;
}
}
return false;
}
public boolean useWhitelist() {
return (accountWhitelistController.isPresent()
&& accountWhitelistController.get().isAccountWhiteListSet());
} }
private Account getSenderAccount( private Account getSenderAccount(

@ -391,7 +391,6 @@ public class TransactionPoolTest {
@Test @Test
public void shouldAllowTransactionWhenAccountWhitelistControllerIsNotPresent() { public void shouldAllowTransactionWhenAccountWhitelistControllerIsNotPresent() {
givenTransactionIsValid(transaction1); givenTransactionIsValid(transaction1);
assertThat(transactionPool.useWhitelist()).isFalse();
assertThat(transactionPool.addLocalTransaction(transaction1)).isEqualTo(valid()); assertThat(transactionPool.addLocalTransaction(transaction1)).isEqualTo(valid());

@ -32,6 +32,7 @@ dependencies {
implementation project(':ethereum:eth') implementation project(':ethereum:eth')
implementation project(':ethereum:p2p') implementation project(':ethereum:p2p')
implementation project(':ethereum:rlp') implementation project(':ethereum:rlp')
implementation project(':ethereum:permissioning')
implementation project(':metrics') implementation project(':metrics')
implementation 'com.google.guava:guava' implementation 'com.google.guava:guava'

@ -35,6 +35,7 @@ import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule;
import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule;
import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSpec; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSpec;
import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController;
import tech.pegasys.pantheon.metrics.MetricsSystem; import tech.pegasys.pantheon.metrics.MetricsSystem;
import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem;
@ -78,6 +79,8 @@ public class JsonRpcTestMethodsFactory {
blockchainQueries, transactionPool, new FilterIdGenerator(), new FilterRepository()); blockchainQueries, transactionPool, new FilterIdGenerator(), new FilterRepository());
final EthHashMiningCoordinator miningCoordinator = mock(EthHashMiningCoordinator.class); final EthHashMiningCoordinator miningCoordinator = mock(EthHashMiningCoordinator.class);
final MetricsSystem metricsSystem = new NoOpMetricsSystem(); final MetricsSystem metricsSystem = new NoOpMetricsSystem();
final AccountWhitelistController accountWhitelistController =
mock(AccountWhitelistController.class);
return new JsonRpcMethodsFactory() return new JsonRpcMethodsFactory()
.methods( .methods(
@ -91,6 +94,7 @@ public class JsonRpcTestMethodsFactory {
miningCoordinator, miningCoordinator,
metricsSystem, metricsSystem,
new HashSet<>(), new HashSet<>(),
accountWhitelistController,
RpcApis.DEFAULT_JSON_RPC_APIS); RpcApis.DEFAULT_JSON_RPC_APIS);
} }
} }

@ -32,6 +32,8 @@ public class JsonRpcErrorConverter {
return JsonRpcError.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE; return JsonRpcError.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE;
case EXCEEDS_BLOCK_GAS_LIMIT: case EXCEEDS_BLOCK_GAS_LIMIT:
return JsonRpcError.EXCEEDS_BLOCK_GAS_LIMIT; return JsonRpcError.EXCEEDS_BLOCK_GAS_LIMIT;
case TX_SENDER_NOT_AUTHORIZED:
return JsonRpcError.TX_SENDER_NOT_AUTHORIZED;
default: default:
return JsonRpcError.INVALID_PARAMS; return JsonRpcError.INVALID_PARAMS;

@ -68,6 +68,9 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.miner.MinerSetCoi
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.miner.MinerSetEtherbase; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.miner.MinerSetEtherbase;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.miner.MinerStart; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.miner.MinerStart;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.miner.MinerStop; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.miner.MinerStop;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.permissioning.PermAddAccountsToWhitelist;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.permissioning.PermGetAccountsWhitelist;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.permissioning.PermRemoveAccountsFromWhitelist;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.BlockReplay; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.BlockReplay;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.TransactionTracer; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.TransactionTracer;
@ -77,6 +80,7 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.results.BlockResultFactor
import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule;
import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController;
import tech.pegasys.pantheon.metrics.MetricsSystem; import tech.pegasys.pantheon.metrics.MetricsSystem;
import java.util.Collection; import java.util.Collection;
@ -101,7 +105,8 @@ public class JsonRpcMethodsFactory {
final MetricsSystem metricsSystem, final MetricsSystem metricsSystem,
final Set<Capability> supportedCapabilities, final Set<Capability> supportedCapabilities,
final Collection<RpcApi> rpcApis, final Collection<RpcApi> rpcApis,
final FilterManager filterManager) { final FilterManager filterManager,
final AccountWhitelistController accountsWhitelistController) {
final BlockchainQueries blockchainQueries = final BlockchainQueries blockchainQueries =
new BlockchainQueries(blockchain, worldStateArchive); new BlockchainQueries(blockchain, worldStateArchive);
return methods( return methods(
@ -115,6 +120,7 @@ public class JsonRpcMethodsFactory {
miningCoordinator, miningCoordinator,
metricsSystem, metricsSystem,
supportedCapabilities, supportedCapabilities,
accountsWhitelistController,
rpcApis); rpcApis);
} }
@ -129,6 +135,7 @@ public class JsonRpcMethodsFactory {
final MiningCoordinator miningCoordinator, final MiningCoordinator miningCoordinator,
final MetricsSystem metricsSystem, final MetricsSystem metricsSystem,
final Set<Capability> supportedCapabilities, final Set<Capability> supportedCapabilities,
final AccountWhitelistController accountsWhitelistController,
final Collection<RpcApi> rpcApis) { final Collection<RpcApi> rpcApis) {
final Map<String, JsonRpcMethod> enabledMethods = new HashMap<>(); final Map<String, JsonRpcMethod> enabledMethods = new HashMap<>();
// @formatter:off // @formatter:off
@ -221,6 +228,13 @@ public class JsonRpcMethodsFactory {
if (rpcApis.contains(RpcApis.ADMIN)) { if (rpcApis.contains(RpcApis.ADMIN)) {
addMethods(enabledMethods, new AdminPeers(p2pNetwork)); addMethods(enabledMethods, new AdminPeers(p2pNetwork));
} }
if (rpcApis.contains(RpcApis.PERM)) {
addMethods(
enabledMethods,
new PermGetAccountsWhitelist(accountsWhitelistController),
new PermAddAccountsToWhitelist(accountsWhitelistController, parameter),
new PermRemoveAccountsFromWhitelist(accountsWhitelistController, parameter));
}
// @formatter:off // @formatter:off
return enabledMethods; return enabledMethods;
} }

@ -23,6 +23,7 @@ public class RpcApis {
public static final RpcApi NET = new RpcApi("NET"); public static final RpcApi NET = new RpcApi("NET");
public static final RpcApi WEB3 = new RpcApi("WEB3"); public static final RpcApi WEB3 = new RpcApi("WEB3");
public static final RpcApi ADMIN = new RpcApi("ADMIN"); public static final RpcApi ADMIN = new RpcApi("ADMIN");
public static final RpcApi PERM = new RpcApi("PERM");
public static final Collection<RpcApi> DEFAULT_JSON_RPC_APIS = Arrays.asList(ETH, NET, WEB3); public static final Collection<RpcApi> DEFAULT_JSON_RPC_APIS = Arrays.asList(ETH, NET, WEB3);
@ -39,6 +40,8 @@ public class RpcApis {
return Optional.of(WEB3); return Optional.of(WEB3);
} else if (name.equals(ADMIN.getCliValue())) { } else if (name.equals(ADMIN.getCliValue())) {
return Optional.of(ADMIN); return Optional.of(ADMIN);
} else if (name.equals(PERM.getCliValue())) {
return Optional.of(PERM);
} else { } else {
return Optional.empty(); return Optional.empty();
} }

@ -0,0 +1,62 @@
/*
* 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.ethereum.jsonrpc.internal.methods.permissioning;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.JsonRpcMethod;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcError;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcErrorResponse;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController.AddResult;
import java.util.List;
public class PermAddAccountsToWhitelist implements JsonRpcMethod {
private final JsonRpcParameter parameters;
private final AccountWhitelistController whitelistController;
public PermAddAccountsToWhitelist(
final AccountWhitelistController whitelistController, final JsonRpcParameter parameters) {
this.whitelistController = whitelistController;
this.parameters = parameters;
}
@Override
public String getName() {
return "perm_addAccountsToWhitelist";
}
@Override
@SuppressWarnings("unchecked")
public JsonRpcResponse response(final JsonRpcRequest request) {
final List<String> accountsList = parameters.required(request.getParams(), 0, List.class);
final AddResult addResult = whitelistController.addAccounts(accountsList);
switch (addResult) {
case ERROR_INVALID_ENTRY:
return new JsonRpcErrorResponse(
request.getId(), JsonRpcError.ACCOUNT_WHITELIST_INVALID_ENTRY);
case ERROR_DUPLICATED_ENTRY:
return new JsonRpcErrorResponse(
request.getId(), JsonRpcError.ACCOUNT_WHITELIST_DUPLICATED_ENTRY);
case SUCCESS:
return new JsonRpcSuccessResponse(request.getId(), true);
default:
throw new IllegalStateException("Unmapped result from AccountWhitelistController");
}
}
}

@ -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.ethereum.jsonrpc.internal.methods.permissioning;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.JsonRpcMethod;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcError;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcErrorResponse;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController;
public class PermGetAccountsWhitelist implements JsonRpcMethod {
private final AccountWhitelistController whitelistController;
public PermGetAccountsWhitelist(final AccountWhitelistController whitelistController) {
this.whitelistController = whitelistController;
}
@Override
public String getName() {
return "perm_getAccountsWhitelist";
}
@Override
public JsonRpcResponse response(final JsonRpcRequest request) {
if (!whitelistController.isAccountWhiteListSet()) {
return new JsonRpcErrorResponse(request.getId(), JsonRpcError.ACCOUNT_WHITELIST_NOT_SET);
} else {
return new JsonRpcSuccessResponse(request.getId(), whitelistController.getAccountWhitelist());
}
}
}

@ -0,0 +1,62 @@
/*
* 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.ethereum.jsonrpc.internal.methods.permissioning;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.JsonRpcMethod;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcError;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcErrorResponse;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController.RemoveResult;
import java.util.List;
public class PermRemoveAccountsFromWhitelist implements JsonRpcMethod {
private final JsonRpcParameter parameters;
private final AccountWhitelistController whitelistController;
public PermRemoveAccountsFromWhitelist(
final AccountWhitelistController whitelistController, final JsonRpcParameter parameters) {
this.whitelistController = whitelistController;
this.parameters = parameters;
}
@Override
public String getName() {
return "perm_removeAccountsFromWhitelist";
}
@Override
@SuppressWarnings("unchecked")
public JsonRpcResponse response(final JsonRpcRequest request) {
final List<String> accountsList = parameters.required(request.getParams(), 0, List.class);
final RemoveResult removeResult = whitelistController.removeAccounts(accountsList);
switch (removeResult) {
case ERROR_INVALID_ENTRY:
return new JsonRpcErrorResponse(
request.getId(), JsonRpcError.ACCOUNT_WHITELIST_INVALID_ENTRY);
case ERROR_ABSENT_ENTRY:
return new JsonRpcErrorResponse(
request.getId(), JsonRpcError.ACCOUNT_WHITELIST_ABSENT_ENTRY);
case SUCCESS:
return new JsonRpcSuccessResponse(request.getId(), true);
default:
throw new IllegalStateException("Unmapped result from AccountWhitelistController");
}
}
}

@ -39,13 +39,20 @@ public enum JsonRpcError {
TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE(-32004, "Upfront cost exceeds account balance"), TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE(-32004, "Upfront cost exceeds account balance"),
EXCEEDS_BLOCK_GAS_LIMIT(-32005, "Transaction gas limit exceeds block gas limit"), EXCEEDS_BLOCK_GAS_LIMIT(-32005, "Transaction gas limit exceeds block gas limit"),
INCORRECT_NONCE(-32006, "Incorrect nonce"), INCORRECT_NONCE(-32006, "Incorrect nonce"),
TX_SENDER_NOT_AUTHORIZED(-32007, "Sender account not authorized to send transactions"),
// Miner failures // Miner failures
COINBASE_NOT_SET(-32010, "Coinbase not set. Unable to start mining without a coinbase."), COINBASE_NOT_SET(-32010, "Coinbase not set. Unable to start mining without a coinbase."),
NO_HASHES_PER_SECOND(-32011, "No hashes being generated by the current node."), NO_HASHES_PER_SECOND(-32011, "No hashes being generated by the current node."),
// Wallet errors // Wallet errors
COINBASE_NOT_SPECIFIED(-32000, "Coinbase must be explicitly specified"); COINBASE_NOT_SPECIFIED(-32000, "Coinbase must be explicitly specified"),
// Permissioning errors
ACCOUNT_WHITELIST_NOT_SET(-32000, "Account whitelist hasn't been set"),
ACCOUNT_WHITELIST_DUPLICATED_ENTRY(-32000, "Account whitelist can't contain duplicated entries"),
ACCOUNT_WHITELIST_ABSENT_ENTRY(-32000, "Can't remove absent account from whitelist"),
ACCOUNT_WHITELIST_INVALID_ENTRY(-32000, "Can't add invalid account address to the whitelist");
private final int code; private final int code;
private final String message; private final String message;

@ -46,6 +46,8 @@ import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSpec;
import tech.pegasys.pantheon.ethereum.mainnet.ValidationResult; import tech.pegasys.pantheon.ethereum.mainnet.ValidationResult;
import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController;
import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration;
import tech.pegasys.pantheon.ethereum.util.RawBlockIterator; import tech.pegasys.pantheon.ethereum.util.RawBlockIterator;
import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem;
@ -165,6 +167,9 @@ public abstract class AbstractEthJsonRpcHttpServiceTest {
supportedCapabilities.add(EthProtocol.ETH62); supportedCapabilities.add(EthProtocol.ETH62);
supportedCapabilities.add(EthProtocol.ETH63); supportedCapabilities.add(EthProtocol.ETH63);
final AccountWhitelistController accountWhitelistController =
new AccountWhitelistController(PermissioningConfiguration.createDefault());
final Map<String, JsonRpcMethod> methods = final Map<String, JsonRpcMethod> methods =
new JsonRpcMethodsFactory() new JsonRpcMethodsFactory()
.methods( .methods(
@ -178,6 +183,7 @@ public abstract class AbstractEthJsonRpcHttpServiceTest {
miningCoordinatorMock, miningCoordinatorMock,
new NoOpMetricsSystem(), new NoOpMetricsSystem(),
supportedCapabilities, supportedCapabilities,
accountWhitelistController,
JSON_RPC_APIS); JSON_RPC_APIS);
final JsonRpcConfiguration config = JsonRpcConfiguration.createDefault(); final JsonRpcConfiguration config = JsonRpcConfiguration.createDefault();
config.setPort(0); config.setPort(0);

@ -27,6 +27,7 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.BlockchainQueries
import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule; import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule;
import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController;
import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem;
import java.io.IOException; import java.io.IOException;
@ -93,6 +94,7 @@ public class JsonRpcHttpServiceHostWhitelistTest {
mock(EthHashMiningCoordinator.class), mock(EthHashMiningCoordinator.class),
new NoOpMetricsSystem(), new NoOpMetricsSystem(),
supportedCapabilities, supportedCapabilities,
mock(AccountWhitelistController.class),
JSON_RPC_APIS)); JSON_RPC_APIS));
service = createJsonRpcHttpService(); service = createJsonRpcHttpService();
service.start().join(); service.start().join();

@ -28,6 +28,7 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcError;
import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule; import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule;
import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController;
import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem;
import java.util.HashSet; import java.util.HashSet;
@ -175,6 +176,7 @@ public class JsonRpcHttpServiceRpcApisTest {
mock(EthHashMiningCoordinator.class), mock(EthHashMiningCoordinator.class),
new NoOpMetricsSystem(), new NoOpMetricsSystem(),
supportedCapabilities, supportedCapabilities,
mock(AccountWhitelistController.class),
config.getRpcApis())); config.getRpcApis()));
final JsonRpcHttpService jsonRpcHttpService = final JsonRpcHttpService jsonRpcHttpService =
new JsonRpcHttpService( new JsonRpcHttpService(

@ -42,6 +42,7 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcError;
import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule; import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule;
import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController;
import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem;
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;
@ -122,6 +123,7 @@ public class JsonRpcHttpServiceTest {
mock(EthHashMiningCoordinator.class), mock(EthHashMiningCoordinator.class),
new NoOpMetricsSystem(), new NoOpMetricsSystem(),
supportedCapabilities, supportedCapabilities,
mock(AccountWhitelistController.class),
JSON_RPC_APIS)); JSON_RPC_APIS));
service = createJsonRpcHttpService(); service = createJsonRpcHttpService();
service.start().join(); service.start().join();

@ -137,6 +137,12 @@ public class EthSendRawTransactionTest {
TransactionInvalidReason.EXCEEDS_BLOCK_GAS_LIMIT, JsonRpcError.EXCEEDS_BLOCK_GAS_LIMIT); TransactionInvalidReason.EXCEEDS_BLOCK_GAS_LIMIT, JsonRpcError.EXCEEDS_BLOCK_GAS_LIMIT);
} }
@Test
public void transactionWithNotWhitelistedSenderAccountIsRejected() {
verifyErrorForInvalidTransaction(
TransactionInvalidReason.TX_SENDER_NOT_AUTHORIZED, JsonRpcError.TX_SENDER_NOT_AUTHORIZED);
}
private void verifyErrorForInvalidTransaction( private void verifyErrorForInvalidTransaction(
final TransactionInvalidReason transactionInvalidReason, final JsonRpcError expectedError) { final TransactionInvalidReason transactionInvalidReason, final JsonRpcError expectedError) {
when(parameter.required(any(Object[].class), anyInt(), any())).thenReturn(VALID_TRANSACTION); when(parameter.required(any(Object[].class), anyInt(), any())).thenReturn(VALID_TRANSACTION);

@ -0,0 +1,105 @@
/*
* 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.ethereum.jsonrpc.internal.methods.permissioning;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.exception.InvalidJsonRpcParameters;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcError;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcErrorResponse;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController.AddResult;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class PermAddAccountsToWhitelistTest {
@Mock private AccountWhitelistController accountWhitelist;
private PermAddAccountsToWhitelist method;
@Before
public void before() {
method = new PermAddAccountsToWhitelist(accountWhitelist, new JsonRpcParameter());
}
@Test
public void getNameShouldReturnExpectedName() {
assertThat(method.getName()).isEqualTo("perm_addAccountsToWhitelist");
}
@Test
public void whenAccountsAreAddedToWhitelistShouldReturnTrue() {
List<String> accounts = Arrays.asList("0x0", "0x1");
JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, true);
when(accountWhitelist.addAccounts(eq(accounts))).thenReturn(AddResult.SUCCESS);
JsonRpcResponse actualResponse = method.response(request(accounts));
assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse);
}
@Test
public void whenAccountIsInvalidShouldReturnInvalidAccountErrorResponse() {
JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(null, JsonRpcError.ACCOUNT_WHITELIST_INVALID_ENTRY);
when(accountWhitelist.addAccounts(any())).thenReturn(AddResult.ERROR_INVALID_ENTRY);
JsonRpcResponse actualResponse = method.response(request(new ArrayList<>()));
assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse);
}
@Test
public void whenAccountIsDuplicatedShouldReturnDuplicatedAccountErrorResponse() {
JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(null, JsonRpcError.ACCOUNT_WHITELIST_DUPLICATED_ENTRY);
when(accountWhitelist.addAccounts(any())).thenReturn(AddResult.ERROR_DUPLICATED_ENTRY);
JsonRpcResponse actualResponse = method.response(request(new ArrayList<>()));
assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse);
}
@Test
public void whenEmptyParamOnRequestShouldThrowInvalidJsonRpcException() {
JsonRpcRequest request =
new JsonRpcRequest("2.0", "perm_addAccountsToWhitelist", new Object[] {});
final Throwable thrown = catchThrowable(() -> method.response(request));
assertThat(thrown)
.hasNoCause()
.isInstanceOf(InvalidJsonRpcParameters.class)
.hasMessage("Missing required json rpc parameter at index 0");
}
private JsonRpcRequest request(final List<String> accounts) {
return new JsonRpcRequest("2.0", "perm_addAccountsToWhitelist", new Object[] {accounts});
}
}

@ -0,0 +1,88 @@
/*
* 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.ethereum.jsonrpc.internal.methods.permissioning;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcError;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcErrorResponse;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class PermGetAccountsWhitelistTest {
private static final JsonRpcRequest request =
new JsonRpcRequest("2.0", "perm_getAccountsWhitelist", null);
@Mock private AccountWhitelistController accountWhitelist;
private PermGetAccountsWhitelist method;
@Before
public void before() {
method = new PermGetAccountsWhitelist(accountWhitelist);
}
@Test
public void getNameShouldReturnExpectedName() {
assertThat(method.getName()).isEqualTo("perm_getAccountsWhitelist");
}
@Test
public void shouldReturnExpectedListOfAccountsWhenWhitelistHasBeenSet() {
List<String> accountsList = Arrays.asList("0x0", "0x1");
when(accountWhitelist.isAccountWhiteListSet()).thenReturn(true);
when(accountWhitelist.getAccountWhitelist()).thenReturn(accountsList);
JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, accountsList);
JsonRpcResponse actualResponse = method.response(request);
assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse);
}
@Test
public void shouldReturnEmptyListOfAccountsWhenWhitelistHasBeenSetAndIsEmpty() {
List<String> emptyAccountsList = new ArrayList<>();
when(accountWhitelist.isAccountWhiteListSet()).thenReturn(true);
when(accountWhitelist.getAccountWhitelist()).thenReturn(emptyAccountsList);
JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, emptyAccountsList);
JsonRpcResponse actualResponse = method.response(request);
assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse);
}
@Test
public void shouldReturnErrorWhenWhitelistHasNotBeenSet() {
when(accountWhitelist.isAccountWhiteListSet()).thenReturn(false);
JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(null, JsonRpcError.ACCOUNT_WHITELIST_NOT_SET);
JsonRpcResponse actualResponse = method.response(request);
assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse);
}
}

@ -0,0 +1,105 @@
/*
* 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.ethereum.jsonrpc.internal.methods.permissioning;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.exception.InvalidJsonRpcParameters;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcError;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcErrorResponse;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController.RemoveResult;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class PermRemoveAccountsFromWhitelistTest {
@Mock private AccountWhitelistController accountWhitelist;
private PermRemoveAccountsFromWhitelist method;
@Before
public void before() {
method = new PermRemoveAccountsFromWhitelist(accountWhitelist, new JsonRpcParameter());
}
@Test
public void getNameShouldReturnExpectedName() {
assertThat(method.getName()).isEqualTo("perm_removeAccountsFromWhitelist");
}
@Test
public void whenAccountsAreRemovedFromWhitelistShouldReturnTrue() {
List<String> accounts = Arrays.asList("0x0", "0x1");
JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, true);
when(accountWhitelist.removeAccounts(eq(accounts))).thenReturn(RemoveResult.SUCCESS);
JsonRpcResponse actualResponse = method.response(request(accounts));
assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse);
}
@Test
public void whenAccountIsInvalidShouldReturnInvalidAccountErrorResponse() {
JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(null, JsonRpcError.ACCOUNT_WHITELIST_INVALID_ENTRY);
when(accountWhitelist.removeAccounts(any())).thenReturn(RemoveResult.ERROR_INVALID_ENTRY);
JsonRpcResponse actualResponse = method.response(request(new ArrayList<>()));
assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse);
}
@Test
public void whenAccountIsAbsentShouldReturnAbsentAccountErrorResponse() {
JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(null, JsonRpcError.ACCOUNT_WHITELIST_ABSENT_ENTRY);
when(accountWhitelist.removeAccounts(any())).thenReturn(RemoveResult.ERROR_ABSENT_ENTRY);
JsonRpcResponse actualResponse = method.response(request(new ArrayList<>()));
assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse);
}
@Test
public void whenEmptyParamOnRequestShouldThrowInvalidJsonRpcException() {
JsonRpcRequest request =
new JsonRpcRequest("2.0", "perm_removeAccountsFromWhitelist", new Object[] {});
final Throwable thrown = catchThrowable(() -> method.response(request));
assertThat(thrown)
.hasNoCause()
.isInstanceOf(InvalidJsonRpcParameters.class)
.hasMessage("Missing required json rpc parameter at index 0");
}
private JsonRpcRequest request(final List<String> accounts) {
return new JsonRpcRequest("2.0", "perm_removeAccountsFromWhitelist", new Object[] {accounts});
}
}

@ -26,6 +26,7 @@ jar {
} }
dependencies { dependencies {
implementation project(':util')
testImplementation 'junit:junit' testImplementation 'junit:junit'
testImplementation 'org.assertj:assertj-core' testImplementation 'org.assertj:assertj-core'

@ -12,40 +12,85 @@
*/ */
package tech.pegasys.pantheon.ethereum.permissioning; package tech.pegasys.pantheon.ethereum.permissioning;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class AccountWhitelistController { public class AccountWhitelistController {
private final List<String> accountWhitelist; private static final int ACCOUNT_BYTES_SIZE = 20;
private boolean accountWhitelistSet = false; private final List<String> accountWhitelist = new ArrayList<>();
private boolean isAccountWhitelistSet = false;
public AccountWhitelistController(final PermissioningConfiguration configuration) { public AccountWhitelistController(final PermissioningConfiguration configuration) {
accountWhitelist = new ArrayList<>(); if (configuration != null && configuration.isAccountWhitelistSet()) {
if (configuration != null && configuration.getAccountWhitelist() != null) { addAccounts(configuration.getAccountWhitelist());
for (String hexString : configuration.getAccountWhitelist()) {
accountWhitelist.add(hexString);
}
if (configuration.isNodeWhitelistSet()) {
accountWhitelistSet = true;
}
} }
} }
public boolean addAccount(final String account) { public AddResult addAccounts(final List<String> accounts) {
accountWhitelistSet = true; if (containsInvalidAccount(accounts)) {
return accountWhitelist.add(account); return AddResult.ERROR_INVALID_ENTRY;
}
boolean hasDuplicatedAccount = accounts.stream().anyMatch(accountWhitelist::contains);
if (hasDuplicatedAccount) {
return AddResult.ERROR_DUPLICATED_ENTRY;
}
this.isAccountWhitelistSet = true;
this.accountWhitelist.addAll(accounts);
return AddResult.SUCCESS;
} }
public boolean removeAccount(final String account) { public RemoveResult removeAccounts(final List<String> accounts) {
return accountWhitelist.remove(account); if (containsInvalidAccount(accounts)) {
return RemoveResult.ERROR_INVALID_ENTRY;
}
if (!accountWhitelist.containsAll(accounts)) {
return RemoveResult.ERROR_ABSENT_ENTRY;
}
this.accountWhitelist.removeAll(accounts);
return RemoveResult.SUCCESS;
} }
public boolean contains(final String account) { public boolean contains(final String account) {
return (!accountWhitelistSet || (accountWhitelistSet && accountWhitelist.contains(account))); return (!isAccountWhitelistSet || accountWhitelist.contains(account));
} }
public boolean isAccountWhiteListSet() { public boolean isAccountWhiteListSet() {
return accountWhitelistSet; return isAccountWhitelistSet;
}
public List<String> getAccountWhitelist() {
return new ArrayList<>(accountWhitelist);
}
private boolean containsInvalidAccount(final List<String> accounts) {
return !accounts.stream().allMatch(this::isValidAccountString);
}
private boolean isValidAccountString(final String account) {
try {
BytesValue bytesValue = BytesValue.fromHexString(account);
return bytesValue.size() == ACCOUNT_BYTES_SIZE;
} catch (NullPointerException | IndexOutOfBoundsException | IllegalArgumentException e) {
return false;
}
}
public enum AddResult {
SUCCESS,
ERROR_DUPLICATED_ENTRY,
ERROR_INVALID_ENTRY
}
public enum RemoveResult {
SUCCESS,
ERROR_ABSENT_ENTRY,
ERROR_INVALID_ENTRY
} }
} }

@ -0,0 +1,142 @@
/*
* 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.ethereum.permissioning;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController.AddResult;
import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController.RemoveResult;
import java.util.ArrayList;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class AccountWhitelistControllerTest {
private AccountWhitelistController controller;
@Mock private PermissioningConfiguration permissioningConfig;
@Before
public void before() {
controller = new AccountWhitelistController(permissioningConfig);
}
@Test
public void newInstanceWithNullPermConfigShouldHaveAccountWhitelistNotSet() {
controller = new AccountWhitelistController(null);
assertThat(controller.isAccountWhiteListSet()).isFalse();
}
@Test
public void whenAccountWhitelistIsNotSetContainsShouldReturnTrue() {
when(permissioningConfig.isAccountWhitelistSet()).thenReturn(false);
controller = new AccountWhitelistController(permissioningConfig);
assertThat(controller.contains("0xfe3b557e8fb62b89f4916b721be55ceb828dbd73")).isTrue();
}
@Test
public void whenPermConfigHasAccountsShouldSetAccountsWhitelist() {
when(permissioningConfig.isAccountWhitelistSet()).thenReturn(true);
when(permissioningConfig.getAccountWhitelist())
.thenReturn(Arrays.asList("0xfe3b557e8fb62b89f4916b721be55ceb828dbd73"));
controller = new AccountWhitelistController(permissioningConfig);
assertThat(controller.isAccountWhiteListSet()).isTrue();
}
@Test
public void whenPermConfigHasAccountsShouldAddAllAccountsToWhitelist() {
when(permissioningConfig.isAccountWhitelistSet()).thenReturn(true);
when(permissioningConfig.getAccountWhitelist())
.thenReturn(Arrays.asList("0xfe3b557e8fb62b89f4916b721be55ceb828dbd73"));
controller = new AccountWhitelistController(permissioningConfig);
assertThat(controller.getAccountWhitelist())
.contains("0xfe3b557e8fb62b89f4916b721be55ceb828dbd73");
}
@Test
public void whenPermConfigContainsEmptyListOfAccountsContainsShouldReturnFalse() {
when(permissioningConfig.isAccountWhitelistSet()).thenReturn(true);
when(permissioningConfig.getAccountWhitelist()).thenReturn(new ArrayList<>());
controller = new AccountWhitelistController(permissioningConfig);
assertThat(controller.contains("0xfe3b557e8fb62b89f4916b721be55ceb828dbd73")).isFalse();
}
@Test
public void addAccountsWithInvalidAccountShouldReturnInvalidEntryResult() {
AddResult addResult = controller.addAccounts(Arrays.asList("0x0"));
assertThat(addResult).isEqualTo(AddResult.ERROR_INVALID_ENTRY);
assertThat(controller.getAccountWhitelist()).isEmpty();
}
@Test
public void addDuplicatedAccountShouldReturnDuplicatedEntryResult() {
controller.addAccounts(Arrays.asList("0xfe3b557e8fb62b89f4916b721be55ceb828dbd73"));
AddResult addResult =
controller.addAccounts(Arrays.asList("0xfe3b557e8fb62b89f4916b721be55ceb828dbd73"));
assertThat(addResult).isEqualTo(AddResult.ERROR_DUPLICATED_ENTRY);
assertThat(controller.getAccountWhitelist())
.containsExactly("0xfe3b557e8fb62b89f4916b721be55ceb828dbd73");
}
@Test
public void addValidAccountsShouldReturnSuccessResult() {
AddResult addResult =
controller.addAccounts(Arrays.asList("0xfe3b557e8fb62b89f4916b721be55ceb828dbd73"));
assertThat(addResult).isEqualTo(AddResult.SUCCESS);
assertThat(controller.getAccountWhitelist())
.containsExactly("0xfe3b557e8fb62b89f4916b721be55ceb828dbd73");
}
@Test
public void removeExistingAccountShouldReturnSuccessResult() {
controller.addAccounts(Arrays.asList("0xfe3b557e8fb62b89f4916b721be55ceb828dbd73"));
RemoveResult removeResult =
controller.removeAccounts(Arrays.asList("0xfe3b557e8fb62b89f4916b721be55ceb828dbd73"));
assertThat(removeResult).isEqualTo(RemoveResult.SUCCESS);
assertThat(controller.getAccountWhitelist()).isEmpty();
}
@Test
public void removeAbsentAccountShouldReturnAbsentEntryResult() {
RemoveResult removeResult =
controller.removeAccounts(Arrays.asList("0xfe3b557e8fb62b89f4916b721be55ceb828dbd73"));
assertThat(removeResult).isEqualTo(RemoveResult.ERROR_ABSENT_ENTRY);
assertThat(controller.getAccountWhitelist()).isEmpty();
}
@Test
public void removeInvalidAccountShouldReturnInvalidEntryResult() {
RemoveResult removeResult = controller.removeAccounts(Arrays.asList("0x0"));
assertThat(removeResult).isEqualTo(RemoveResult.ERROR_INVALID_ENTRY);
assertThat(controller.getAccountWhitelist()).isEmpty();
}
}

@ -230,9 +230,9 @@ public class RunnerBuilder {
final TransactionPool transactionPool = pantheonController.getTransactionPool(); final TransactionPool transactionPool = pantheonController.getTransactionPool();
final MiningCoordinator miningCoordinator = pantheonController.getMiningCoordinator(); final MiningCoordinator miningCoordinator = pantheonController.getMiningCoordinator();
final AccountWhitelistController accountWhitelistController =
new AccountWhitelistController(permissioningConfiguration);
if (permissioningConfiguration.isAccountWhitelistSet()) { if (permissioningConfiguration.isAccountWhitelistSet()) {
AccountWhitelistController accountWhitelistController =
new AccountWhitelistController(permissioningConfiguration);
transactionPool.setAccountWhitelist(accountWhitelistController); transactionPool.setAccountWhitelist(accountWhitelistController);
} }
@ -252,7 +252,8 @@ public class RunnerBuilder {
metricsSystem, metricsSystem,
supportedCapabilities, supportedCapabilities,
jsonRpcConfiguration.getRpcApis(), jsonRpcConfiguration.getRpcApis(),
filterManager); filterManager,
accountWhitelistController);
jsonRpcHttpService = jsonRpcHttpService =
Optional.of( Optional.of(
new JsonRpcHttpService( new JsonRpcHttpService(
@ -273,7 +274,8 @@ public class RunnerBuilder {
metricsSystem, metricsSystem,
supportedCapabilities, supportedCapabilities,
webSocketConfiguration.getRpcApis(), webSocketConfiguration.getRpcApis(),
filterManager); filterManager,
accountWhitelistController);
final SubscriptionManager subscriptionManager = final SubscriptionManager subscriptionManager =
createSubscriptionManager( createSubscriptionManager(
@ -331,7 +333,8 @@ public class RunnerBuilder {
final MetricsSystem metricsSystem, final MetricsSystem metricsSystem,
final Set<Capability> supportedCapabilities, final Set<Capability> supportedCapabilities,
final Collection<RpcApi> jsonRpcApis, final Collection<RpcApi> jsonRpcApis,
final FilterManager filterManager) { final FilterManager filterManager,
final AccountWhitelistController accountWhitelistController) {
final Map<String, JsonRpcMethod> methods = final Map<String, JsonRpcMethod> methods =
new JsonRpcMethodsFactory() new JsonRpcMethodsFactory()
.methods( .methods(
@ -346,7 +349,8 @@ public class RunnerBuilder {
metricsSystem, metricsSystem,
supportedCapabilities, supportedCapabilities,
jsonRpcApis, jsonRpcApis,
filterManager); filterManager,
accountWhitelistController);
methods.putAll(pantheonController.getAdditionalJsonRpcMethods(jsonRpcApis)); methods.putAll(pantheonController.getAdditionalJsonRpcMethods(jsonRpcApis));
return methods; return methods;
} }

Loading…
Cancel
Save