diff --git a/acceptance-tests/build.gradle b/acceptance-tests/build.gradle index 20bca6a1cc..75f4a4af65 100644 --- a/acceptance-tests/build.gradle +++ b/acceptance-tests/build.gradle @@ -23,7 +23,7 @@ dependencies { testImplementation project(':ethereum:core') testImplementation project(':ethereum:blockcreation') testImplementation project(':ethereum:jsonrpc') - testImplementation project(':ethereum:p2p') + testImplementation project(':ethereum:permissioning') testImplementation project(':metrics') testImplementation project(':pantheon') testImplementation project(':util') diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java index ded13b8c2e..ead225ad97 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java @@ -22,7 +22,7 @@ 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.websocket.WebSocketConfiguration; -import tech.pegasys.pantheon.ethereum.p2p.config.PermissioningConfiguration; +import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; 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; diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ProcessPantheonNodeRunner.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ProcessPantheonNodeRunner.java index 00f50b2588..fa9dc38b6e 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ProcessPantheonNodeRunner.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ProcessPantheonNodeRunner.java @@ -17,7 +17,7 @@ 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.RpcApis; -import tech.pegasys.pantheon.ethereum.p2p.config.PermissioningConfiguration; +import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; import java.io.File; import java.io.IOException; diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfiguration.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfiguration.java index cd2dde68eb..9eae43e3fe 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfiguration.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfiguration.java @@ -15,7 +15,7 @@ package tech.pegasys.pantheon.tests.acceptance.dsl.node.factory; import tech.pegasys.pantheon.ethereum.core.MiningParameters; import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration; -import tech.pegasys.pantheon.ethereum.p2p.config.PermissioningConfiguration; +import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; import tech.pegasys.pantheon.tests.acceptance.dsl.node.GenesisConfigProvider; class PantheonFactoryConfiguration { diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfigurationBuilder.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfigurationBuilder.java index a7ff09bf2c..642c07f098 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfigurationBuilder.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonFactoryConfigurationBuilder.java @@ -18,7 +18,7 @@ import tech.pegasys.pantheon.ethereum.core.MiningParameters; import tech.pegasys.pantheon.ethereum.core.MiningParametersTestBuilder; import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration; -import tech.pegasys.pantheon.ethereum.p2p.config.PermissioningConfiguration; +import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; import tech.pegasys.pantheon.tests.acceptance.dsl.node.GenesisConfigProvider; import java.util.Optional; diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonNodeFactory.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonNodeFactory.java index 7878faff57..f1eacd4a5d 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonNodeFactory.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/factory/PantheonNodeFactory.java @@ -22,7 +22,7 @@ import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration; import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApi; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration; -import tech.pegasys.pantheon.ethereum.p2p.config.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.RunnableNode; diff --git a/ethereum/core/build.gradle b/ethereum/core/build.gradle index 1a3c5f2071..d23e2acb05 100644 --- a/ethereum/core/build.gradle +++ b/ethereum/core/build.gradle @@ -30,6 +30,7 @@ dependencies { implementation project(':crypto') implementation project(':ethereum:rlp') implementation project(':ethereum:trie') + implementation project(':ethereum:permissioning') implementation project(':metrics') implementation project(':services:kvstore') diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/TransactionPool.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/TransactionPool.java index 8f2d004d15..19c4b52c2e 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/TransactionPool.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/TransactionPool.java @@ -24,12 +24,14 @@ import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.ethereum.mainnet.TransactionValidator; import tech.pegasys.pantheon.ethereum.mainnet.TransactionValidator.TransactionInvalidReason; import tech.pegasys.pantheon.ethereum.mainnet.ValidationResult; +import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import org.apache.logging.log4j.Logger; @@ -47,6 +49,7 @@ public class TransactionPool implements BlockAddedObserver { private final ProtocolSchedule protocolSchedule; private final ProtocolContext protocolContext; private final TransactionBatchAddedListener transactionBatchAddedListener; + private Optional accountWhitelistController = Optional.empty(); public TransactionPool( final PendingTransactions pendingTransactions, @@ -131,6 +134,13 @@ public class TransactionPool implements BlockAddedObserver { return basicValidationResult; } + String sender = transaction.getSender().toString(); + if (accountIsNotWhitelisted(sender)) { + return ValidationResult.invalid( + TransactionInvalidReason.TX_SENDER_NOT_AUTHORIZED, + String.format("Sender %s is not on the Account Whitelist", sender)); + } + final BlockHeader chainHeadBlockHeader = getChainHeadBlockHeader(); if (transaction.getGasLimit() > chainHeadBlockHeader.getGasLimit()) { return ValidationResult.invalid( @@ -147,6 +157,20 @@ public class TransactionPool implements BlockAddedObserver { pendingTransactions.getNextNonceForSender(transaction.getSender())); } + private boolean accountIsNotWhitelisted(final String account) { + if (useWhitelist()) { + if (!accountWhitelistController.get().contains(account)) { + return true; + } + } + return false; + } + + public boolean useWhitelist() { + return (accountWhitelistController.isPresent() + && accountWhitelistController.get().isAccountWhiteListSet()); + } + private Account getSenderAccount( final Transaction transaction, final BlockHeader chainHeadHeader) { final WorldState worldState = @@ -163,4 +187,8 @@ public class TransactionPool implements BlockAddedObserver { void onTransactionsAdded(Iterable transactions); } + + public void setAccountWhitelist(AccountWhitelistController accountWhitelist) { + accountWhitelistController = Optional.of(accountWhitelist); + } } diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/TransactionValidator.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/TransactionValidator.java index 9ecea7cfea..eb3643d583 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/TransactionValidator.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/TransactionValidator.java @@ -55,6 +55,7 @@ public interface TransactionValidator { NONCE_TOO_LOW, INCORRECT_NONCE, INTRINSIC_GAS_EXCEEDS_GAS_LIMIT, - EXCEEDS_BLOCK_GAS_LIMIT + EXCEEDS_BLOCK_GAS_LIMIT, + TX_SENDER_NOT_AUTHORIZED } } diff --git a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/core/TransactionPoolTest.java b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/core/TransactionPoolTest.java index e72b1b0413..309f502e30 100644 --- a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/core/TransactionPoolTest.java +++ b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/core/TransactionPoolTest.java @@ -30,6 +30,7 @@ import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static tech.pegasys.pantheon.ethereum.mainnet.TransactionValidator.TransactionInvalidReason.EXCEEDS_BLOCK_GAS_LIMIT; import static tech.pegasys.pantheon.ethereum.mainnet.TransactionValidator.TransactionInvalidReason.NONCE_TOO_LOW; +import static tech.pegasys.pantheon.ethereum.mainnet.TransactionValidator.TransactionInvalidReason.TX_SENDER_NOT_AUTHORIZED; import static tech.pegasys.pantheon.ethereum.mainnet.ValidationResult.valid; import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; @@ -40,6 +41,7 @@ import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSpec; import tech.pegasys.pantheon.ethereum.mainnet.TransactionValidator; import tech.pegasys.pantheon.ethereum.mainnet.ValidationResult; +import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController; import tech.pegasys.pantheon.util.uint.UInt256; import java.util.List; @@ -70,6 +72,8 @@ public class TransactionPoolTest { private final Transaction transaction2 = createTransaction(2); private TransactionPool transactionPool; private long genesisBlockGasLimit; + private final AccountWhitelistController accountWhitelistController = + mock(AccountWhitelistController.class); @Before public void setUp() { @@ -355,6 +359,45 @@ public class TransactionPoolTest { verifyZeroInteractions(batchAddedListener); } + @Test + public void shouldAllowWhitelistedTransactionWhenWhitelistEnabled() { + transactionPool.setAccountWhitelist(accountWhitelistController); + givenTransactionIsValid(transaction1); + + when(accountWhitelistController.isAccountWhiteListSet()).thenReturn(true); + when(accountWhitelistController.contains(transaction1.getSender().toString())).thenReturn(true); + + assertThat(transactionPool.addLocalTransaction(transaction1)).isEqualTo(valid()); + + assertTransactionPending(transaction1); + } + + @Test + public void shouldRejectNonWhitelistedTransactionWhenWhitelistEnabled() { + transactionPool.setAccountWhitelist(accountWhitelistController); + givenTransactionIsValid(transaction1); + + when(accountWhitelistController.isAccountWhiteListSet()).thenReturn(true); + when(accountWhitelistController.contains(transaction1.getSender().toString())) + .thenReturn(false); + + assertThat(transactionPool.addLocalTransaction(transaction1)) + .isEqualTo(ValidationResult.invalid(TX_SENDER_NOT_AUTHORIZED)); + + assertTransactionNotPending(transaction1); + verifyZeroInteractions(batchAddedListener); + } + + @Test + public void shouldAllowTransactionWhenAccountWhitelistControllerIsNotPresent() { + givenTransactionIsValid(transaction1); + assertThat(transactionPool.useWhitelist()).isFalse(); + + assertThat(transactionPool.addLocalTransaction(transaction1)).isEqualTo(valid()); + + assertTransactionPending(transaction1); + } + private void assertTransactionPending(final Transaction t) { assertThat(transactions.getTransactionByHash(t.hash())).contains(t); } diff --git a/ethereum/eth/build.gradle b/ethereum/eth/build.gradle index 1035578843..931817b3b7 100644 --- a/ethereum/eth/build.gradle +++ b/ethereum/eth/build.gradle @@ -29,6 +29,7 @@ dependencies { implementation project(':ethereum:core') implementation project(':ethereum:p2p') implementation project(':ethereum:rlp') + implementation project(':ethereum:permissioning') implementation project(':metrics') implementation project(':services:kvstore') diff --git a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/transactions/TestNode.java b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/transactions/TestNode.java index c0867bfc8f..99c70e729a 100644 --- a/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/transactions/TestNode.java +++ b/ethereum/eth/src/test/java/tech/pegasys/pantheon/ethereum/eth/transactions/TestNode.java @@ -37,7 +37,6 @@ import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection; import tech.pegasys.pantheon.ethereum.p2p.config.DiscoveryConfiguration; import tech.pegasys.pantheon.ethereum.p2p.config.NetworkingConfiguration; -import tech.pegasys.pantheon.ethereum.p2p.config.PermissioningConfiguration; import tech.pegasys.pantheon.ethereum.p2p.config.RlpxConfiguration; import tech.pegasys.pantheon.ethereum.p2p.netty.NettyP2PNetwork; import tech.pegasys.pantheon.ethereum.p2p.peers.DefaultPeer; @@ -46,6 +45,7 @@ import tech.pegasys.pantheon.ethereum.p2p.peers.Peer; import tech.pegasys.pantheon.ethereum.p2p.peers.PeerBlacklist; import tech.pegasys.pantheon.ethereum.p2p.permissioning.NodeWhitelistController; import tech.pegasys.pantheon.ethereum.p2p.wire.messages.DisconnectMessage.DisconnectReason; +import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; import tech.pegasys.pantheon.util.bytes.BytesValue; diff --git a/ethereum/p2p/build.gradle b/ethereum/p2p/build.gradle index d321d5c463..a17e6453d1 100644 --- a/ethereum/p2p/build.gradle +++ b/ethereum/p2p/build.gradle @@ -28,6 +28,7 @@ jar { dependencies { implementation project(':crypto') implementation project(':ethereum:core') + implementation project(':ethereum:permissioning') implementation project(':ethereum:rlp') implementation project(':metrics') diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/permissioning/NodeWhitelistController.java b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/permissioning/NodeWhitelistController.java index 15024c3e03..783700a4a2 100644 --- a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/permissioning/NodeWhitelistController.java +++ b/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/permissioning/NodeWhitelistController.java @@ -12,9 +12,9 @@ */ package tech.pegasys.pantheon.ethereum.p2p.permissioning; -import tech.pegasys.pantheon.ethereum.p2p.config.PermissioningConfiguration; import tech.pegasys.pantheon.ethereum.p2p.peers.DefaultPeer; import tech.pegasys.pantheon.ethereum.p2p.peers.Peer; +import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; import java.net.URI; import java.util.ArrayList; diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/NettyP2PNetworkTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/NettyP2PNetworkTest.java index 9c0455b6e5..9fd070fd04 100644 --- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/NettyP2PNetworkTest.java +++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/NettyP2PNetworkTest.java @@ -25,7 +25,6 @@ import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection; import tech.pegasys.pantheon.ethereum.p2p.config.DiscoveryConfiguration; import tech.pegasys.pantheon.ethereum.p2p.config.NetworkingConfiguration; -import tech.pegasys.pantheon.ethereum.p2p.config.PermissioningConfiguration; import tech.pegasys.pantheon.ethereum.p2p.config.RlpxConfiguration; import tech.pegasys.pantheon.ethereum.p2p.netty.NettyP2PNetwork; import tech.pegasys.pantheon.ethereum.p2p.netty.exceptions.IncompatiblePeerException; @@ -38,6 +37,7 @@ import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.p2p.wire.PeerInfo; import tech.pegasys.pantheon.ethereum.p2p.wire.SubProtocol; import tech.pegasys.pantheon.ethereum.p2p.wire.messages.DisconnectMessage.DisconnectReason; +import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; import tech.pegasys.pantheon.util.bytes.BytesValue; diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/NetworkingServiceLifecycleTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/NetworkingServiceLifecycleTest.java index a78546cf1d..02a3faca23 100644 --- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/NetworkingServiceLifecycleTest.java +++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/NetworkingServiceLifecycleTest.java @@ -22,11 +22,11 @@ import tech.pegasys.pantheon.crypto.SECP256K1; import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; import tech.pegasys.pantheon.ethereum.p2p.config.DiscoveryConfiguration; import tech.pegasys.pantheon.ethereum.p2p.config.NetworkingConfiguration; -import tech.pegasys.pantheon.ethereum.p2p.config.PermissioningConfiguration; import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryServiceException; import tech.pegasys.pantheon.ethereum.p2p.netty.NettyP2PNetwork; import tech.pegasys.pantheon.ethereum.p2p.peers.PeerBlacklist; import tech.pegasys.pantheon.ethereum.p2p.permissioning.NodeWhitelistController; +import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; import tech.pegasys.pantheon.util.NetworkUtility; diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/AbstractPeerDiscoveryTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/AbstractPeerDiscoveryTest.java index 3051e404d7..c75c6f7da0 100644 --- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/AbstractPeerDiscoveryTest.java +++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/AbstractPeerDiscoveryTest.java @@ -16,13 +16,13 @@ import static io.vertx.core.Vertx.vertx; import tech.pegasys.pantheon.crypto.SECP256K1; import tech.pegasys.pantheon.ethereum.p2p.config.DiscoveryConfiguration; -import tech.pegasys.pantheon.ethereum.p2p.config.PermissioningConfiguration; import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.Packet; import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PacketType; import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PingPacketData; import tech.pegasys.pantheon.ethereum.p2p.peers.Endpoint; import tech.pegasys.pantheon.ethereum.p2p.peers.PeerBlacklist; import tech.pegasys.pantheon.ethereum.p2p.permissioning.NodeWhitelistController; +import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; import tech.pegasys.pantheon.util.bytes.BytesValue; import java.util.List; diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryAgentTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryAgentTest.java index aac2b15583..2cc9a9ea9c 100644 --- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryAgentTest.java +++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryAgentTest.java @@ -19,7 +19,6 @@ import tech.pegasys.pantheon.crypto.SECP256K1; import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; import tech.pegasys.pantheon.ethereum.p2p.api.PeerConnection; import tech.pegasys.pantheon.ethereum.p2p.config.DiscoveryConfiguration; -import tech.pegasys.pantheon.ethereum.p2p.config.PermissioningConfiguration; import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.FindNeighborsPacketData; import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.NeighborsPacketData; import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.Packet; @@ -31,6 +30,7 @@ import tech.pegasys.pantheon.ethereum.p2p.permissioning.NodeWhitelistController; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.p2p.wire.PeerInfo; import tech.pegasys.pantheon.ethereum.p2p.wire.messages.DisconnectMessage.DisconnectReason; +import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; import tech.pegasys.pantheon.util.bytes.BytesValue; import java.net.SocketAddress; diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryObserversTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryObserversTest.java index 1505c60b5f..a6fd0080f0 100644 --- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryObserversTest.java +++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryObserversTest.java @@ -19,11 +19,11 @@ import static org.awaitility.Awaitility.await; import static tech.pegasys.pantheon.ethereum.p2p.NetworkingTestHelper.configWithRandomPorts; import tech.pegasys.pantheon.crypto.SECP256K1; -import tech.pegasys.pantheon.ethereum.p2p.config.PermissioningConfiguration; import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryEvent.PeerBondedEvent; import tech.pegasys.pantheon.ethereum.p2p.peers.Peer; import tech.pegasys.pantheon.ethereum.p2p.peers.PeerBlacklist; import tech.pegasys.pantheon.ethereum.p2p.permissioning.NodeWhitelistController; +import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; import java.util.ArrayList; import java.util.Collections; diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryTimestampsTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryTimestampsTest.java index c284064f2a..98f34ec3c9 100644 --- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryTimestampsTest.java +++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/PeerDiscoveryTimestampsTest.java @@ -18,7 +18,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import tech.pegasys.pantheon.crypto.SECP256K1; -import tech.pegasys.pantheon.ethereum.p2p.config.PermissioningConfiguration; import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.Packet; import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PacketType; import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PeerDiscoveryController; @@ -26,6 +25,7 @@ import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PeerTable; import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PingPacketData; import tech.pegasys.pantheon.ethereum.p2p.peers.PeerBlacklist; import tech.pegasys.pantheon.ethereum.p2p.permissioning.NodeWhitelistController; +import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; import java.util.Collections; import java.util.concurrent.TimeUnit; diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryControllerTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryControllerTest.java index 5fb85c6bbc..033ccdf0eb 100644 --- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryControllerTest.java +++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryControllerTest.java @@ -28,7 +28,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import tech.pegasys.pantheon.crypto.SECP256K1; -import tech.pegasys.pantheon.ethereum.p2p.config.PermissioningConfiguration; import tech.pegasys.pantheon.ethereum.p2p.discovery.DiscoveryPeer; import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryAgent; import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryStatus; @@ -37,6 +36,7 @@ import tech.pegasys.pantheon.ethereum.p2p.peers.Endpoint; import tech.pegasys.pantheon.ethereum.p2p.peers.Peer; import tech.pegasys.pantheon.ethereum.p2p.peers.PeerBlacklist; import tech.pegasys.pantheon.ethereum.p2p.permissioning.NodeWhitelistController; +import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; import tech.pegasys.pantheon.util.bytes.Bytes32; import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.MutableBytesValue; diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryTableRefreshTest.java b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryTableRefreshTest.java index 9698b771ee..e3da76d570 100644 --- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryTableRefreshTest.java +++ b/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryTableRefreshTest.java @@ -23,12 +23,12 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import tech.pegasys.pantheon.crypto.SECP256K1; -import tech.pegasys.pantheon.ethereum.p2p.config.PermissioningConfiguration; import tech.pegasys.pantheon.ethereum.p2p.discovery.DiscoveryPeer; import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryAgent; import tech.pegasys.pantheon.ethereum.p2p.discovery.PeerDiscoveryTestHelper; import tech.pegasys.pantheon.ethereum.p2p.peers.PeerBlacklist; import tech.pegasys.pantheon.ethereum.p2p.permissioning.NodeWhitelistController; +import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; import tech.pegasys.pantheon.util.bytes.BytesValue; import java.util.ArrayList; diff --git a/ethereum/permissioning/build.gradle b/ethereum/permissioning/build.gradle new file mode 100644 index 0000000000..67d9b72de1 --- /dev/null +++ b/ethereum/permissioning/build.gradle @@ -0,0 +1,33 @@ +/* + * 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. + */ + +apply plugin: 'java-library' + +jar { + baseName 'pantheon-permissioning' + manifest { + attributes( + 'Specification-Title': baseName, + 'Specification-Version': project.version, + 'Implementation-Title': baseName, + 'Implementation-Version': calculateVersion() + ) + } +} + +dependencies { + + testImplementation 'junit:junit' + testImplementation 'org.assertj:assertj-core' + testImplementation 'org.mockito:mockito-core' +} diff --git a/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/AccountWhitelistController.java b/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/AccountWhitelistController.java new file mode 100644 index 0000000000..b782719797 --- /dev/null +++ b/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/AccountWhitelistController.java @@ -0,0 +1,51 @@ +/* + * 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 java.util.ArrayList; +import java.util.List; + +public class AccountWhitelistController { + + private final List accountWhitelist; + private boolean accountWhitelistSet = false; + + public AccountWhitelistController(final PermissioningConfiguration configuration) { + accountWhitelist = new ArrayList<>(); + if (configuration != null && configuration.getAccountWhitelist() != null) { + for (String hexString : configuration.getAccountWhitelist()) { + accountWhitelist.add(hexString); + } + if (configuration.isNodeWhitelistSet()) { + accountWhitelistSet = true; + } + } + } + + public boolean addAccount(final String account) { + accountWhitelistSet = true; + return accountWhitelist.add(account); + } + + public boolean removeAccount(final String account) { + return accountWhitelist.remove(account); + } + + public boolean contains(final String account) { + return (!accountWhitelistSet || (accountWhitelistSet && accountWhitelist.contains(account))); + } + + public boolean isAccountWhiteListSet() { + return accountWhitelistSet; + } +} diff --git a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/config/PermissioningConfiguration.java b/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/PermissioningConfiguration.java similarity index 70% rename from ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/config/PermissioningConfiguration.java rename to ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/PermissioningConfiguration.java index b5469e8866..97c0d62e1d 100644 --- a/ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/config/PermissioningConfiguration.java +++ b/ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/PermissioningConfiguration.java @@ -10,7 +10,7 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package tech.pegasys.pantheon.ethereum.p2p.config; +package tech.pegasys.pantheon.ethereum.permissioning; import java.net.URI; import java.util.ArrayList; @@ -19,7 +19,9 @@ import java.util.List; public class PermissioningConfiguration { private List nodeWhitelist; + private List accountWhitelist; private boolean nodeWhitelistSet; + private boolean accountWhitelistSet; public List getNodeWhitelist() { return nodeWhitelist; @@ -28,6 +30,7 @@ public class PermissioningConfiguration { public static PermissioningConfiguration createDefault() { final PermissioningConfiguration config = new PermissioningConfiguration(); config.nodeWhitelist = new ArrayList<>(); + config.accountWhitelist = new ArrayList<>(); return config; } @@ -42,7 +45,18 @@ public class PermissioningConfiguration { return nodeWhitelistSet; } - public boolean contains(final URI node) { - return !isNodeWhitelistSet() || nodeWhitelist.contains(node); + public List getAccountWhitelist() { + return accountWhitelist; + } + + public void setAccountWhitelist(final Collection accountWhitelist) { + if (accountWhitelist != null) { + this.accountWhitelist.addAll(accountWhitelist); + this.accountWhitelistSet = true; + } + } + + public boolean isAccountWhitelistSet() { + return accountWhitelistSet; } } diff --git a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/config/PermissioningConfigurationTest.java b/ethereum/permissioning/src/test/java/tech/pegasys/pantheon/ethereum/permissioning/PermissioningConfigurationTest.java similarity index 67% rename from ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/config/PermissioningConfigurationTest.java rename to ethereum/permissioning/src/test/java/tech/pegasys/pantheon/ethereum/permissioning/PermissioningConfigurationTest.java index a5fc177441..edf24da466 100644 --- a/ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/config/PermissioningConfigurationTest.java +++ b/ethereum/permissioning/src/test/java/tech/pegasys/pantheon/ethereum/permissioning/PermissioningConfigurationTest.java @@ -10,7 +10,7 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package tech.pegasys.pantheon.ethereum.p2p.config; +package tech.pegasys.pantheon.ethereum.permissioning; import static org.assertj.core.api.Assertions.assertThat; @@ -32,6 +32,8 @@ public class PermissioningConfigurationTest { final PermissioningConfiguration configuration = PermissioningConfiguration.createDefault(); assertThat(configuration.getNodeWhitelist()).isEmpty(); assertThat(configuration.isNodeWhitelistSet()).isFalse(); + assertThat(configuration.getAccountWhitelist()).isEmpty(); + assertThat(configuration.isAccountWhitelistSet()).isFalse(); } @Test @@ -43,7 +45,7 @@ public class PermissioningConfigurationTest { } @Test - public void setNodeWhiteListPassingNullShouldNotChangeWhitelistSetFlag() { + public void setNodeWhiteListPassingNull() { final PermissioningConfiguration configuration = PermissioningConfiguration.createDefault(); configuration.setNodeWhitelist(null); assertThat(configuration.getNodeWhitelist()).isEmpty(); @@ -51,9 +53,19 @@ public class PermissioningConfigurationTest { } @Test - public void contains() { + public void setAccountWhitelist() { + final String[] accounts = {"1111111111111111", "2222222222222222", "ffffffffffffffff"}; final PermissioningConfiguration configuration = PermissioningConfiguration.createDefault(); - configuration.setNodeWhitelist(Arrays.asList(nodes)); - assertThat(configuration.contains(URI.create("enode://001@123:4567"))).isTrue(); + configuration.setAccountWhitelist(Arrays.asList(accounts)); + assertThat(configuration.getAccountWhitelist()).containsExactlyInAnyOrder(accounts); + assertThat(configuration.isAccountWhitelistSet()).isTrue(); + } + + @Test + public void setAccountWhiteListPassingNull() { + final PermissioningConfiguration configuration = PermissioningConfiguration.createDefault(); + configuration.setAccountWhitelist(null); + assertThat(configuration.getAccountWhitelist()).isEmpty(); + assertThat(configuration.isAccountWhitelistSet()).isFalse(); } } diff --git a/pantheon/build.gradle b/pantheon/build.gradle index 93a0bcacbf..c00aca8701 100644 --- a/pantheon/build.gradle +++ b/pantheon/build.gradle @@ -36,6 +36,7 @@ dependencies { implementation project(':ethereum:core') implementation project(':ethereum:eth') implementation project(':ethereum:jsonrpc') + implementation project(':ethereum:permissioning') implementation project(':ethereum:p2p') implementation project(':ethereum:rlp') implementation project(':metrics') diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java b/pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java index 8d5849d04f..709fcc77c1 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java @@ -43,7 +43,6 @@ import tech.pegasys.pantheon.ethereum.p2p.NetworkRunner; import tech.pegasys.pantheon.ethereum.p2p.api.ProtocolManager; import tech.pegasys.pantheon.ethereum.p2p.config.DiscoveryConfiguration; import tech.pegasys.pantheon.ethereum.p2p.config.NetworkingConfiguration; -import tech.pegasys.pantheon.ethereum.p2p.config.PermissioningConfiguration; import tech.pegasys.pantheon.ethereum.p2p.config.RlpxConfiguration; import tech.pegasys.pantheon.ethereum.p2p.config.SubProtocolConfiguration; import tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PeerRequirement; @@ -52,6 +51,8 @@ import tech.pegasys.pantheon.ethereum.p2p.peers.PeerBlacklist; import tech.pegasys.pantheon.ethereum.p2p.permissioning.NodeWhitelistController; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.p2p.wire.SubProtocol; +import tech.pegasys.pantheon.ethereum.permissioning.AccountWhitelistController; +import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; import tech.pegasys.pantheon.metrics.MetricsSystem; import tech.pegasys.pantheon.util.bytes.BytesValue; @@ -221,6 +222,12 @@ public class RunnerBuilder { final TransactionPool transactionPool = pantheonController.getTransactionPool(); final MiningCoordinator miningCoordinator = pantheonController.getMiningCoordinator(); + if (permissioningConfiguration.isAccountWhitelistSet()) { + AccountWhitelistController accountWhitelistController = + new AccountWhitelistController(permissioningConfiguration); + transactionPool.setAccountWhitelist(accountWhitelistController); + } + final FilterManager filterManager = createFilterManager(vertx, context, transactionPool); Optional jsonRpcHttpService = Optional.empty(); diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java index ce6da4248a..2b9ad9a99f 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java @@ -35,9 +35,9 @@ 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.p2p.config.DiscoveryConfiguration; -import tech.pegasys.pantheon.ethereum.p2p.config.PermissioningConfiguration; import tech.pegasys.pantheon.ethereum.p2p.peers.DefaultPeer; import tech.pegasys.pantheon.ethereum.p2p.peers.Peer; +import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; import tech.pegasys.pantheon.ethereum.util.InvalidConfigurationException; import tech.pegasys.pantheon.metrics.MetricsSystem; import tech.pegasys.pantheon.metrics.prometheus.PrometheusMetricsSystem; @@ -404,13 +404,25 @@ public class PantheonCommand implements DefaultCommandValues, Runnable { names = {"--nodes-whitelist"}, paramLabel = "", description = - "Comma separated enode URLs for permissioned networks. Not intended to be used with mainnet or public testnets.", + "Comma separated enode URLs for permissioned networks. " + + "Not intended to be used with mainnet or public testnets.", split = ",", arity = "0..*", converter = EnodeToURIPropertyConverter.class ) private final Collection nodesWhitelist = null; + @Option( + names = {"--accounts-whitelist"}, + paramLabel = "", + description = + "Comma separated hex strings of account public keys " + + "for permissioned/role-based transactions. You may specify an empty list.", + split = ",", + arity = "0..*" + ) + private final Collection accountsWhitelist = null; + public PantheonCommand( final BlockImporter blockImporter, final RunnerBuilder runnerBuilder, @@ -570,6 +582,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable { final PermissioningConfiguration permissioningConfiguration = PermissioningConfiguration.createDefault(); permissioningConfiguration.setNodeWhitelist(nodesWhitelist); + permissioningConfiguration.setAccountWhitelist(accountsWhitelist); return permissioningConfiguration; } diff --git a/pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java b/pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java index 75aeb29a66..3ec242522b 100644 --- a/pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java +++ b/pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java @@ -33,8 +33,8 @@ import tech.pegasys.pantheon.ethereum.mainnet.HeaderValidationMode; import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSpec; -import tech.pegasys.pantheon.ethereum.p2p.config.PermissioningConfiguration; import tech.pegasys.pantheon.ethereum.p2p.peers.DefaultPeer; +import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; import tech.pegasys.pantheon.ethereum.storage.StorageProvider; import tech.pegasys.pantheon.ethereum.storage.keyvalue.RocksDbStorageProvider; import tech.pegasys.pantheon.metrics.MetricsSystem; diff --git a/pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java b/pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java index c4757b2310..bda77602b3 100644 --- a/pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java +++ b/pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java @@ -22,7 +22,7 @@ import tech.pegasys.pantheon.controller.PantheonController; import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration; import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration; -import tech.pegasys.pantheon.ethereum.p2p.config.PermissioningConfiguration; +import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration; import tech.pegasys.pantheon.util.BlockImporter; import java.io.ByteArrayOutputStream; diff --git a/pantheon/src/test/java/tech/pegasys/pantheon/cli/PantheonCommandTest.java b/pantheon/src/test/java/tech/pegasys/pantheon/cli/PantheonCommandTest.java index 0af4f5b79d..f6bb3f20df 100644 --- a/pantheon/src/test/java/tech/pegasys/pantheon/cli/PantheonCommandTest.java +++ b/pantheon/src/test/java/tech/pegasys/pantheon/cli/PantheonCommandTest.java @@ -533,6 +533,40 @@ public class PantheonCommandTest extends CommandTestAbstract { assertThat(commandErrorOutput.toString()).isEmpty(); } + @Test + public void callingWithAccountsWhitelistOptionButNoValueMustNotError() { + parseCommand("--accounts-whitelist"); + + verify(mockRunnerBuilder) + .permissioningConfiguration(permissioningConfigurationArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(permissioningConfigurationArgumentCaptor.getValue().getAccountWhitelist()).isEmpty(); + assertThat(permissioningConfigurationArgumentCaptor.getValue().isAccountWhitelistSet()) + .isTrue(); + + assertThat(commandOutput.toString()).isEmpty(); + assertThat(commandErrorOutput.toString()).isEmpty(); + } + + @Test + public void accountsWhitelistOptionMustBeUsed() { + final String[] accounts = {"1111111111111111", "2222222222222222", "ffffffffffffffff"}; + parseCommand("--accounts-whitelist", String.join(",", accounts)); + + verify(mockRunnerBuilder) + .permissioningConfiguration(permissioningConfigurationArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(permissioningConfigurationArgumentCaptor.getValue().getAccountWhitelist()) + .containsExactlyInAnyOrder(accounts); + assertThat(permissioningConfigurationArgumentCaptor.getValue().isAccountWhitelistSet()) + .isTrue(); + + assertThat(commandOutput.toString()).isEmpty(); + assertThat(commandErrorOutput.toString()).isEmpty(); + } + @Test public void nodesWhitelistOptionMustIncludeBootnodes() { parseCommand( diff --git a/settings.gradle b/settings.gradle index f6bfe1a300..bb30fba135 100644 --- a/settings.gradle +++ b/settings.gradle @@ -25,6 +25,7 @@ include 'crypto' include 'ethereum:p2p' include 'ethereum:mock-p2p' include 'ethereum:jsonrpc' +include 'ethereum:permissioning' include 'ethereum:referencetests' include 'ethereum:core' include 'ethereum:blockcreation'