PAN-2445: Onchain account permissioning (#1507)

Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>
pull/2/head
Lucas Saldanha 6 years ago committed by GitHub
parent c7d74690f5
commit 576c12dd46
  1. 1
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetTransactionValidator.java
  2. 7
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MutableProtocolSchedule.java
  3. 4
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/ProtocolSchedule.java
  4. 5
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/ProtocolSpec.java
  5. 3
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/TransactionValidator.java
  6. 8
      ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetTransactionValidatorTest.java
  7. 18
      ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/AccountLocalConfigPermissioningController.java
  8. 19
      ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/TransactionSmartContractPermissioningController.java
  9. 52
      ethereum/permissioning/src/main/java/tech/pegasys/pantheon/ethereum/permissioning/account/AccountPermissioningController.java
  10. 4
      ethereum/permissioning/src/test/java/tech/pegasys/pantheon/ethereum/permissioning/TransactionSmartContractPermissioningControllerTest.java
  11. 5
      ethereum/permissioning/src/test/java/tech/pegasys/pantheon/ethereum/permissioning/account/AccountPermissioningControllerTest.java
  12. 32
      pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java
  13. 132
      pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java
  14. 14
      pantheon/src/test/java/tech/pegasys/pantheon/cli/CommandTestAbstract.java
  15. 93
      pantheon/src/test/java/tech/pegasys/pantheon/cli/PantheonCommandTest.java
  16. 2
      pantheon/src/test/resources/everything_config.toml

@ -166,6 +166,7 @@ public class MainnetTransactionValidator implements TransactionValidator {
return transactionFilter.map(c -> c.permitted(transaction, isStateChange)).orElse(true);
}
@Override
public void setTransactionFilter(final TransactionFilter transactionFilter) {
this.transactionFilter = Optional.of(transactionFilter);
}

@ -14,6 +14,8 @@ package tech.pegasys.pantheon.ethereum.mainnet;
import static com.google.common.base.Preconditions.checkArgument;
import tech.pegasys.pantheon.ethereum.core.TransactionFilter;
import java.math.BigInteger;
import java.util.Comparator;
import java.util.NavigableSet;
@ -69,4 +71,9 @@ public class MutableProtocolSchedule<C> implements ProtocolSchedule<C> {
.map(spec -> spec.getSpec().getName() + ": " + spec.getBlock())
.collect(Collectors.joining(", ", "[", "]"));
}
@Override
public void setTransactionFilter(final TransactionFilter transactionFilter) {
protocolSpecs.forEach(spec -> spec.getSpec().setTransactionFilter(transactionFilter));
}
}

@ -12,6 +12,8 @@
*/
package tech.pegasys.pantheon.ethereum.mainnet;
import tech.pegasys.pantheon.ethereum.core.TransactionFilter;
import java.math.BigInteger;
import java.util.Optional;
@ -20,4 +22,6 @@ public interface ProtocolSchedule<C> {
ProtocolSpec<C> getByBlockNumber(long number);
Optional<BigInteger> getChainId();
void setTransactionFilter(TransactionFilter transactionFilter);
}

@ -15,6 +15,7 @@ package tech.pegasys.pantheon.ethereum.mainnet;
import tech.pegasys.pantheon.ethereum.BlockValidator;
import tech.pegasys.pantheon.ethereum.core.BlockHeaderFunctions;
import tech.pegasys.pantheon.ethereum.core.BlockImporter;
import tech.pegasys.pantheon.ethereum.core.TransactionFilter;
import tech.pegasys.pantheon.ethereum.core.Wei;
import tech.pegasys.pantheon.ethereum.mainnet.MainnetBlockProcessor.TransactionReceiptFactory;
import tech.pegasys.pantheon.ethereum.vm.EVM;
@ -243,4 +244,8 @@ public class ProtocolSpec<C> {
public PrecompileContractRegistry getPrecompileContractRegistry() {
return precompileContractRegistry;
}
public void setTransactionFilter(final TransactionFilter transactionFilter) {
transactionValidator.setTransactionFilter(transactionFilter);
}
}

@ -14,6 +14,7 @@ package tech.pegasys.pantheon.ethereum.mainnet;
import tech.pegasys.pantheon.ethereum.core.Account;
import tech.pegasys.pantheon.ethereum.core.Transaction;
import tech.pegasys.pantheon.ethereum.core.TransactionFilter;
/** Validates transaction based on some criteria. */
public interface TransactionValidator {
@ -54,6 +55,8 @@ public interface TransactionValidator {
ValidationResult<TransactionInvalidReason> validateForSender(
Transaction transaction, Account sender, TransactionValidationParams validationParams);
void setTransactionFilter(TransactionFilter transactionFilter);
enum TransactionInvalidReason {
WRONG_CHAIN_ID,
REPLAY_PROTECTED_SIGNATURES_NOT_SUPPORTED,

@ -158,7 +158,6 @@ public class MainnetTransactionValidatorTest {
public void shouldRejectTransactionIfAccountIsNotPermitted() {
final MainnetTransactionValidator validator =
new MainnetTransactionValidator(gasCalculator, false, Optional.empty());
validator.setTransactionFilter(transactionFilter(false));
assertThat(validator.validateForSender(basicTransaction, accountWithNonce(0), true))
@ -169,7 +168,6 @@ public class MainnetTransactionValidatorTest {
public void shouldAcceptValidTransactionIfAccountIsPermitted() {
final MainnetTransactionValidator validator =
new MainnetTransactionValidator(gasCalculator, false, Optional.empty());
validator.setTransactionFilter(transactionFilter(true));
assertThat(validator.validateForSender(basicTransaction, accountWithNonce(0), true))
@ -178,13 +176,13 @@ public class MainnetTransactionValidatorTest {
@Test
public void shouldPropagateCorrectStateChangeParamToTransactionFilter() {
final MainnetTransactionValidator validator =
new MainnetTransactionValidator(gasCalculator, false, Optional.empty());
final ArgumentCaptor<Boolean> stateChangeParamCaptor = ArgumentCaptor.forClass(Boolean.class);
final TransactionFilter transactionFilter = mock(TransactionFilter.class);
when(transactionFilter.permitted(any(Transaction.class), stateChangeParamCaptor.capture()))
.thenReturn(true);
final MainnetTransactionValidator validator =
new MainnetTransactionValidator(gasCalculator, false, Optional.empty());
validator.setTransactionFilter(transactionFilter);
final TransactionValidationParams validationParams =

@ -13,6 +13,7 @@
package tech.pegasys.pantheon.ethereum.permissioning;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.core.Transaction;
import tech.pegasys.pantheon.ethereum.permissioning.account.TransactionPermissioningProvider;
import tech.pegasys.pantheon.metrics.Counter;
@ -232,17 +233,32 @@ public class AccountLocalConfigPermissioningController implements TransactionPer
@Override
public boolean isPermitted(final Transaction transaction) {
this.checkCounter.inc();
final Hash transactionHash = transaction.hash();
final Address sender = transaction.getSender();
LOG.trace("Account permissioning - Local Config: Checking transaction {}", transactionHash);
this.checkCounter.inc();
if (sender == null) {
this.checkCounterUnpermitted.inc();
LOG.trace(
"Account permissioning - Local Config: Rejected transaction {} without sender",
transactionHash);
return false;
} else {
if (contains(sender.toString())) {
this.checkCounterPermitted.inc();
LOG.trace(
"Account permissioning - Local Config: Permitted transaction {} from {}",
transactionHash,
sender);
return true;
} else {
this.checkCounterUnpermitted.inc();
LOG.trace(
"Account permissioning - Local Config: Rejected transaction {} from {}",
transactionHash,
sender);
return false;
}
}

@ -30,12 +30,18 @@ import tech.pegasys.pantheon.util.bytes.BytesValues;
import java.util.Optional;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Controller that can read from a smart contract that exposes the permissioning call
* transactionAllowed(address,address,uint256,uint256,uint256,bytes)
*/
public class TransactionSmartContractPermissioningController
implements TransactionPermissioningProvider {
private static final Logger LOG = LogManager.getLogger();
private final Address contractAddress;
private final TransactionSimulator transactionSimulator;
@ -101,6 +107,11 @@ public class TransactionSmartContractPermissioningController
*/
@Override
public boolean isPermitted(final Transaction transaction) {
final tech.pegasys.pantheon.ethereum.core.Hash transactionHash = transaction.hash();
final Address sender = transaction.getSender();
LOG.trace("Account permissioning - Smart Contract : Checking transaction {}", transactionHash);
this.checkCounter.inc();
final BytesValue payload = createPayload(transaction);
final CallParameter callParams =
@ -131,9 +142,17 @@ public class TransactionSmartContractPermissioningController
if (result.map(r -> checkTransactionResult(r.getOutput())).orElse(false)) {
this.checkCounterPermitted.inc();
LOG.trace(
"Account permissioning - Smart Contract: Permitted transaction {} from {}",
transactionHash,
sender);
return true;
} else {
this.checkCounterUnpermitted.inc();
LOG.trace(
"Account permissioning - Smart Contract: Rejected transaction {} from {}",
transactionHash,
sender);
return false;
}
}

@ -12,19 +12,30 @@
*/
package tech.pegasys.pantheon.ethereum.permissioning.account;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.core.Transaction;
import tech.pegasys.pantheon.ethereum.permissioning.AccountLocalConfigPermissioningController;
import tech.pegasys.pantheon.ethereum.permissioning.TransactionSmartContractPermissioningController;
import java.util.Optional;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class AccountPermissioningController {
private final AccountLocalConfigPermissioningController accountLocalConfigPermissioningController;
private final TransactionSmartContractPermissioningController
private static final Logger LOG = LogManager.getLogger();
private final Optional<AccountLocalConfigPermissioningController>
accountLocalConfigPermissioningController;
private final Optional<TransactionSmartContractPermissioningController>
transactionSmartContractPermissioningController;
public AccountPermissioningController(
final AccountLocalConfigPermissioningController accountLocalConfigPermissioningController,
final TransactionSmartContractPermissioningController
final Optional<AccountLocalConfigPermissioningController>
accountLocalConfigPermissioningController,
final Optional<TransactionSmartContractPermissioningController>
transactionSmartContractPermissioningController) {
this.accountLocalConfigPermissioningController = accountLocalConfigPermissioningController;
this.transactionSmartContractPermissioningController =
@ -32,11 +43,38 @@ public class AccountPermissioningController {
}
public boolean isPermitted(final Transaction transaction, final boolean includeOnChainCheck) {
final Hash transactionHash = transaction.hash();
final Address sender = transaction.getSender();
LOG.trace("Account permissioning: Checking transaction {}", transactionHash);
boolean permitted;
if (includeOnChainCheck) {
return accountLocalConfigPermissioningController.isPermitted(transaction)
&& transactionSmartContractPermissioningController.isPermitted(transaction);
permitted =
accountLocalConfigPermissioningController
.map(c -> c.isPermitted(transaction))
.orElse(true)
&& transactionSmartContractPermissioningController
.map(c -> c.isPermitted(transaction))
.orElse(true);
} else {
return accountLocalConfigPermissioningController.isPermitted(transaction);
permitted =
accountLocalConfigPermissioningController
.map(c -> c.isPermitted(transaction))
.orElse(true);
}
if (permitted) {
LOG.trace("Account permissioning: Permitted transaction {} from {}", transactionHash, sender);
} else {
LOG.trace("Account permissioning: Rejected transaction {} from {}", transactionHash, sender);
}
return permitted;
}
public Optional<AccountLocalConfigPermissioningController>
getAccountLocalConfigPermissioningController() {
return accountLocalConfigPermissioningController;
}
}

@ -22,6 +22,7 @@ import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.create
import static tech.pegasys.pantheon.ethereum.core.InMemoryStorageProvider.createInMemoryWorldStateArchive;
import tech.pegasys.pantheon.config.GenesisConfigFile;
import tech.pegasys.pantheon.crypto.SECP256K1.Signature;
import tech.pegasys.pantheon.ethereum.chain.GenesisState;
import tech.pegasys.pantheon.ethereum.chain.MutableBlockchain;
import tech.pegasys.pantheon.ethereum.core.Address;
@ -37,6 +38,7 @@ import tech.pegasys.pantheon.metrics.MetricsSystem;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.io.IOException;
import java.math.BigInteger;
import com.google.common.io.Resources;
import org.junit.Test;
@ -97,6 +99,8 @@ public class TransactionSmartContractPermissioningControllerTest {
.gasPrice(Wei.ZERO)
.gasLimit(0)
.payload(BytesValue.EMPTY)
.nonce(1)
.signature(Signature.create(BigInteger.ONE, BigInteger.TEN, (byte) 1))
.build();
}

@ -23,6 +23,8 @@ import tech.pegasys.pantheon.ethereum.core.Transaction;
import tech.pegasys.pantheon.ethereum.permissioning.AccountLocalConfigPermissioningController;
import tech.pegasys.pantheon.ethereum.permissioning.TransactionSmartContractPermissioningController;
import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -40,7 +42,8 @@ public class AccountPermissioningControllerTest {
@Before
public void before() {
permissioningController =
new AccountPermissioningController(localConfigController, smartContractController);
new AccountPermissioningController(
Optional.of(localConfigController), Optional.of(smartContractController));
}
@Test

@ -62,10 +62,10 @@ import tech.pegasys.pantheon.ethereum.p2p.permissions.PeerPermissionsBlacklist;
import tech.pegasys.pantheon.ethereum.p2p.wire.Capability;
import tech.pegasys.pantheon.ethereum.p2p.wire.SubProtocol;
import tech.pegasys.pantheon.ethereum.permissioning.AccountLocalConfigPermissioningController;
import tech.pegasys.pantheon.ethereum.permissioning.LocalPermissioningConfiguration;
import tech.pegasys.pantheon.ethereum.permissioning.NodeLocalConfigPermissioningController;
import tech.pegasys.pantheon.ethereum.permissioning.NodePermissioningControllerFactory;
import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration;
import tech.pegasys.pantheon.ethereum.permissioning.account.AccountPermissioningController;
import tech.pegasys.pantheon.ethereum.permissioning.node.NodePermissioningController;
import tech.pegasys.pantheon.ethereum.transaction.TransactionSimulator;
import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive;
@ -110,6 +110,7 @@ public class RunnerBuilder {
private MetricsSystem metricsSystem;
private Optional<PermissioningConfiguration> permissioningConfiguration = Optional.empty();
private Collection<EnodeURL> staticNodes = Collections.emptyList();
private AccountPermissioningController accountPermissioningController;
public RunnerBuilder vertx(final Vertx vertx) {
this.vertx = vertx;
@ -197,6 +198,12 @@ public class RunnerBuilder {
return this;
}
public RunnerBuilder accountPermissioningController(
final AccountPermissioningController accountPermissioningController) {
this.accountPermissioningController = accountPermissioningController;
return this;
}
public Runner build() {
Preconditions.checkNotNull(pantheonController);
@ -245,9 +252,6 @@ public class RunnerBuilder {
final List<EnodeURL> bootnodes = discoveryConfiguration.getBootnodes();
final Optional<LocalPermissioningConfiguration> localPermissioningConfiguration =
permissioningConfiguration.flatMap(PermissioningConfiguration::getLocalConfig);
final Synchronizer synchronizer = pantheonController.getSynchronizer();
final TransactionSimulator transactionSimulator =
@ -289,16 +293,6 @@ public class RunnerBuilder {
final TransactionPool transactionPool = pantheonController.getTransactionPool();
final MiningCoordinator miningCoordinator = pantheonController.getMiningCoordinator();
final Optional<AccountLocalConfigPermissioningController> accountWhitelistController =
localPermissioningConfiguration
.filter(LocalPermissioningConfiguration::isAccountWhitelistEnabled)
.map(
configuration -> {
final AccountLocalConfigPermissioningController whitelistController =
new AccountLocalConfigPermissioningController(configuration, metricsSystem);
transactionPool.setAccountFilter(whitelistController::contains);
return whitelistController;
});
final PrivacyParameters privacyParameters = pantheonController.getPrivacyParameters();
final FilterManager filterManager = createFilterManager(vertx, context, transactionPool);
@ -312,6 +306,12 @@ public class RunnerBuilder {
final Optional<NodeLocalConfigPermissioningController> nodeLocalConfigPermissioningController =
nodePermissioningController.flatMap(NodePermissioningController::localConfigController);
final Optional<AccountLocalConfigPermissioningController>
accountLocalConfigPermissioningController =
accountPermissioningController != null
? accountPermissioningController.getAccountLocalConfigPermissioningController()
: Optional.empty();
Optional<JsonRpcHttpService> jsonRpcHttpService = Optional.empty();
if (jsonRpcConfiguration.isEnabled()) {
final Map<String, JsonRpcMethod> jsonRpcMethods =
@ -327,7 +327,7 @@ public class RunnerBuilder {
supportedCapabilities,
jsonRpcConfiguration.getRpcApis(),
filterManager,
accountWhitelistController,
accountLocalConfigPermissioningController,
nodeLocalConfigPermissioningController,
privacyParameters,
jsonRpcConfiguration,
@ -378,7 +378,7 @@ public class RunnerBuilder {
supportedCapabilities,
webSocketConfiguration.getRpcApis(),
filterManager,
accountWhitelistController,
accountLocalConfigPermissioningController,
nodeLocalConfigPermissioningController,
privacyParameters,
jsonRpcConfiguration,

@ -40,6 +40,7 @@ import tech.pegasys.pantheon.cli.rlp.RLPSubCommand;
import tech.pegasys.pantheon.config.GenesisConfigFile;
import tech.pegasys.pantheon.controller.KeyPairUtil;
import tech.pegasys.pantheon.controller.PantheonController;
import tech.pegasys.pantheon.ethereum.ProtocolContext;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.MiningParameters;
import tech.pegasys.pantheon.ethereum.core.PrivacyParameters;
@ -54,12 +55,17 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration;
import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApi;
import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApis;
import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration;
import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule;
import tech.pegasys.pantheon.ethereum.p2p.config.DiscoveryConfiguration;
import tech.pegasys.pantheon.ethereum.p2p.peers.StaticNodesParser;
import tech.pegasys.pantheon.ethereum.permissioning.AccountLocalConfigPermissioningController;
import tech.pegasys.pantheon.ethereum.permissioning.LocalPermissioningConfiguration;
import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration;
import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfigurationBuilder;
import tech.pegasys.pantheon.ethereum.permissioning.SmartContractPermissioningConfiguration;
import tech.pegasys.pantheon.ethereum.permissioning.TransactionSmartContractPermissioningController;
import tech.pegasys.pantheon.ethereum.permissioning.account.AccountPermissioningController;
import tech.pegasys.pantheon.ethereum.transaction.TransactionSimulator;
import tech.pegasys.pantheon.metrics.MetricCategory;
import tech.pegasys.pantheon.metrics.MetricsSystem;
import tech.pegasys.pantheon.metrics.prometheus.MetricsConfiguration;
@ -514,6 +520,18 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
description = "Enable node level permissions via smart contract (default: ${DEFAULT-VALUE})")
private final Boolean permissionsNodesContractEnabled = false;
@Option(
names = {"--permissions-accounts-contract-address"},
description = "Address of the account permissioning smart contract",
arity = "1")
private final Address permissionsAccountsContractAddress = null;
@Option(
names = {"--permissions-accounts-contract-enabled"},
description =
"Enable account level permissions via smart contract (default: ${DEFAULT-VALUE})")
private final Boolean permissionsAccountsContractEnabled = false;
@Option(
names = {"--privacy-enabled"},
description = "Enable private transactions (default: ${DEFAULT-VALUE})")
@ -733,6 +751,14 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
final PantheonController<?> pantheonController = buildController();
final MetricsConfiguration metricsConfiguration = metricsConfiguration();
final AccountPermissioningController accountPermissioningController =
buildAccountPermissioningController(permissioningConfiguration, pantheonController);
if (permissionsAccountsEnabled || permissionsAccountsContractEnabled) {
pantheonController
.getProtocolSchedule()
.setTransactionFilter(accountPermissioningController::isPermitted);
}
pantheonPluginContext.addService(
PantheonEvents.class,
new PantheonEventsImpl((pantheonController.getProtocolManager().getBlockBroadcaster())));
@ -751,12 +777,64 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
webSocketConfiguration,
metricsConfiguration,
permissioningConfiguration,
staticNodes);
staticNodes,
accountPermissioningController);
} catch (final Exception e) {
throw new ParameterException(this.commandLine, e.getMessage(), e);
}
}
private AccountPermissioningController buildAccountPermissioningController(
final Optional<PermissioningConfiguration> permissioningConfiguration,
final PantheonController<?> pantheonController) {
Optional<AccountLocalConfigPermissioningController> accountLocalConfigPermissioningController =
Optional.empty();
Optional<TransactionSmartContractPermissioningController>
transactionSmartContractPermissioningController = Optional.empty();
if (permissioningConfiguration.isPresent()) {
final PermissioningConfiguration config = permissioningConfiguration.get();
if (config.getLocalConfig().isPresent()) {
final LocalPermissioningConfiguration localPermissioningConfiguration =
config.getLocalConfig().get();
if (localPermissioningConfiguration.isAccountWhitelistEnabled()) {
accountLocalConfigPermissioningController =
Optional.of(
new AccountLocalConfigPermissioningController(
localPermissioningConfiguration, metricsSystem.get()));
}
}
if (config.getSmartContractConfig().isPresent()) {
final SmartContractPermissioningConfiguration smartContractPermissioningConfiguration =
config.getSmartContractConfig().get();
if (smartContractPermissioningConfiguration.isSmartContractAccountWhitelistEnabled()) {
final Address accountSmartContractAddress =
smartContractPermissioningConfiguration.getAccountSmartContractAddress();
final ProtocolContext<?> protocolContext = pantheonController.getProtocolContext();
final ProtocolSchedule<?> protocolSchedule = pantheonController.getProtocolSchedule();
final TransactionSimulator transactionSimulator =
new TransactionSimulator(
protocolContext.getBlockchain(),
protocolContext.getWorldStateArchive(),
protocolSchedule);
transactionSmartContractPermissioningController =
Optional.of(
new TransactionSmartContractPermissioningController(
accountSmartContractAddress, transactionSimulator, metricsSystem.get()));
}
}
}
return new AccountPermissioningController(
accountLocalConfigPermissioningController, transactionSmartContractPermissioningController);
}
private NetworkName getNetwork() {
// noinspection ConstantConditions network is not always null but injected by
// PicoCLI if used
@ -924,10 +1002,6 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
}
private Optional<PermissioningConfiguration> permissioningConfiguration() throws Exception {
final Optional<LocalPermissioningConfiguration> localPermissioningConfigurationOptional;
final Optional<SmartContractPermissioningConfiguration>
smartContractPermissioningConfigurationOptional;
if (!(localPermissionsEnabled() || contractPermissionsEnabled())) {
if (rpcHttpApis.contains(RpcApis.PERM) || rpcWsApis.contains(RpcApis.PERM)) {
logger.warn(
@ -936,6 +1010,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
return Optional.empty();
}
final Optional<LocalPermissioningConfiguration> localPermissioningConfigurationOptional;
if (localPermissionsEnabled()) {
final Optional<String> nodePermissioningConfigFile =
Optional.ofNullable(nodePermissionsConfigFile());
@ -965,30 +1040,46 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
localPermissioningConfigurationOptional = Optional.empty();
}
if (contractPermissionsEnabled()) {
final SmartContractPermissioningConfiguration smartContractPermissioningConfiguration =
SmartContractPermissioningConfiguration.createDefault();
if (permissionsNodesContractEnabled) {
if (permissionsNodesContractAddress == null) {
throw new ParameterException(
this.commandLine,
"No contract address specified. Cannot enable contract based permissions.");
}
final SmartContractPermissioningConfiguration smartContractPermissioningConfiguration =
PermissioningConfigurationBuilder.smartContractPermissioningConfiguration(
permissionsNodesContractAddress, permissionsNodesContractEnabled);
smartContractPermissioningConfigurationOptional =
Optional.of(smartContractPermissioningConfiguration);
"No node permissioning contract address specified. Cannot enable smart contract based node permissioning.");
} else {
if (permissionsNodesContractAddress != null) {
smartContractPermissioningConfiguration.setSmartContractNodeWhitelistEnabled(
permissionsNodesContractEnabled);
smartContractPermissioningConfiguration.setNodeSmartContractAddress(
permissionsNodesContractAddress);
}
} else if (permissionsNodesContractAddress != null) {
logger.warn(
"Smart contract address set {} but no contract permissions enabled",
"Node permissioning smart contract address set {} but smart contract node permissioning is disabled.",
permissionsNodesContractAddress);
}
smartContractPermissioningConfigurationOptional = Optional.empty();
if (permissionsAccountsContractEnabled) {
if (permissionsAccountsContractAddress == null) {
throw new ParameterException(
this.commandLine,
"No account permissioning contract address specified. Cannot enable smart contract based account permissioning.");
} else {
smartContractPermissioningConfiguration.setSmartContractAccountWhitelistEnabled(
permissionsAccountsContractEnabled);
smartContractPermissioningConfiguration.setAccountSmartContractAddress(
permissionsAccountsContractAddress);
}
} else if (permissionsAccountsContractAddress != null) {
logger.warn(
"Account permissioning smart contract address set {} but smart contract account permissioning is disabled.",
permissionsAccountsContractAddress);
}
final PermissioningConfiguration permissioningConfiguration =
new PermissioningConfiguration(
localPermissioningConfigurationOptional,
smartContractPermissioningConfigurationOptional);
Optional.of(smartContractPermissioningConfiguration));
return Optional.of(permissioningConfiguration);
}
@ -998,8 +1089,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
}
private boolean contractPermissionsEnabled() {
// TODO add permissionsAccountsContractEnabled
return permissionsNodesContractEnabled;
return permissionsNodesContractEnabled || permissionsAccountsContractEnabled;
}
private PrivacyParameters privacyParameters() throws IOException {
@ -1054,7 +1144,8 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
final WebSocketConfiguration webSocketConfiguration,
final MetricsConfiguration metricsConfiguration,
final Optional<PermissioningConfiguration> permissioningConfiguration,
final Collection<EnodeURL> staticNodes) {
final Collection<EnodeURL> staticNodes,
final AccountPermissioningController accountPermissioningController) {
checkNotNull(runnerBuilder);
@ -1079,6 +1170,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
.metricsSystem(metricsSystem)
.metricsConfiguration(metricsConfiguration)
.staticNodes(staticNodes)
.accountPermissioningController(accountPermissioningController)
.build();
addShutdownHook(runner);

@ -17,6 +17,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.when;
import tech.pegasys.pantheon.Runner;
@ -25,6 +26,7 @@ import tech.pegasys.pantheon.cli.PublicKeySubCommand.KeyLoader;
import tech.pegasys.pantheon.controller.PantheonController;
import tech.pegasys.pantheon.controller.PantheonControllerBuilder;
import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair;
import tech.pegasys.pantheon.ethereum.ProtocolContext;
import tech.pegasys.pantheon.ethereum.eth.EthereumWireProtocolConfiguration;
import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManager;
import tech.pegasys.pantheon.ethereum.eth.sync.BlockBroadcaster;
@ -32,7 +34,9 @@ import tech.pegasys.pantheon.ethereum.eth.sync.SynchronizerConfiguration;
import tech.pegasys.pantheon.ethereum.graphql.GraphQLConfiguration;
import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration;
import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration;
import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule;
import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration;
import tech.pegasys.pantheon.ethereum.permissioning.account.AccountPermissioningController;
import tech.pegasys.pantheon.metrics.prometheus.MetricsConfiguration;
import tech.pegasys.pantheon.services.PantheonPluginContextImpl;
import tech.pegasys.pantheon.services.kvstore.RocksDbConfiguration;
@ -80,6 +84,8 @@ public abstract class CommandTestAbstract {
@Mock PantheonControllerBuilder<Void> mockControllerBuilder;
@Mock EthProtocolManager mockEthProtocolManager;
@Mock ProtocolSchedule<Object> mockProtocolSchedule;
@Mock ProtocolContext<Object> mockProtocolContext;
@Mock BlockBroadcaster mockBlockBroadcaster;
@Mock SynchronizerConfiguration.Builder mockSyncConfBuilder;
@Mock EthereumWireProtocolConfiguration.Builder mockEthereumWireProtocolConfigurationBuilder;
@ -105,6 +111,9 @@ public abstract class CommandTestAbstract {
@Captor ArgumentCaptor<PermissioningConfiguration> permissioningConfigurationArgumentCaptor;
@Captor
ArgumentCaptor<AccountPermissioningController> accountPermissioningControllerArgumentCaptor;
@Rule public final TemporaryFolder temp = new TemporaryFolder();
@Before
@ -134,7 +143,9 @@ public abstract class CommandTestAbstract {
// doReturn used because of generic PantheonController
doReturn(mockController).when(mockControllerBuilder).build();
when(mockController.getProtocolManager()).thenReturn(mockEthProtocolManager);
lenient().when(mockController.getProtocolManager()).thenReturn(mockEthProtocolManager);
lenient().when(mockController.getProtocolSchedule()).thenReturn(mockProtocolSchedule);
lenient().when(mockController.getProtocolContext()).thenReturn(mockProtocolContext);
when(mockEthProtocolManager.getBlockBroadcaster()).thenReturn(mockBlockBroadcaster);
@ -165,6 +176,7 @@ public abstract class CommandTestAbstract {
when(mockRunnerBuilder.metricsSystem(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.metricsConfiguration(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.staticNodes(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.accountPermissioningController(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.build()).thenReturn(mockRunner);
}

@ -17,6 +17,7 @@ import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNotNull;
import static org.mockito.Mockito.atLeast;
@ -48,6 +49,7 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration;
import tech.pegasys.pantheon.ethereum.permissioning.LocalPermissioningConfiguration;
import tech.pegasys.pantheon.ethereum.permissioning.PermissioningConfiguration;
import tech.pegasys.pantheon.ethereum.permissioning.SmartContractPermissioningConfiguration;
import tech.pegasys.pantheon.ethereum.permissioning.account.AccountPermissioningController;
import tech.pegasys.pantheon.metrics.MetricCategory;
import tech.pegasys.pantheon.metrics.prometheus.MetricsConfiguration;
import tech.pegasys.pantheon.util.bytes.BytesValue;
@ -339,7 +341,7 @@ public class PantheonCommandTest extends CommandTestAbstract {
}
@Test
public void permissionsSmartContractWithoutOptionMustError() {
public void nodePermissionsSmartContractWithoutOptionMustError() {
parseCommand("--permissions-nodes-contract-address");
verifyZeroInteractions(mockRunnerBuilder);
@ -350,17 +352,18 @@ public class PantheonCommandTest extends CommandTestAbstract {
}
@Test
public void permissionsEnabledWithoutContractAddressMustError() {
public void nodePermissionsEnabledWithoutContractAddressMustError() {
parseCommand("--permissions-nodes-contract-enabled");
verifyZeroInteractions(mockRunnerBuilder);
assertThat(commandErrorOutput.toString()).contains("No contract address specified");
assertThat(commandErrorOutput.toString())
.contains("No node permissioning contract address specified");
assertThat(commandOutput.toString()).isEmpty();
}
@Test
public void permissionsEnabledWithInvalidContractAddressMustError() {
public void nodePermissionsEnabledWithInvalidContractAddressMustError() {
parseCommand(
"--permissions-nodes-contract-enabled",
"--permissions-nodes-contract-address",
@ -373,7 +376,7 @@ public class PantheonCommandTest extends CommandTestAbstract {
}
@Test
public void permissionsEnabledWithTooShortContractAddressMustError() {
public void nodePermissionsEnabledWithTooShortContractAddressMustError() {
parseCommand(
"--permissions-nodes-contract-enabled", "--permissions-nodes-contract-address", "0x1234");
@ -384,7 +387,7 @@ public class PantheonCommandTest extends CommandTestAbstract {
}
@Test
public void permissionsSmartContractMustUseOption() {
public void nodePermissionsSmartContractMustUseOption() {
String smartContractAddress = "0x0000000000000000000000000000000000001234";
@ -410,6 +413,75 @@ public class PantheonCommandTest extends CommandTestAbstract {
assertThat(commandOutput.toString()).isEmpty();
}
@Test
public void accountPermissionsSmartContractWithoutOptionMustError() {
parseCommand("--permissions-accounts-contract-address");
verifyZeroInteractions(mockRunnerBuilder);
assertThat(commandErrorOutput.toString())
.startsWith(
"Missing required parameter for option '--permissions-accounts-contract-address'");
assertThat(commandOutput.toString()).isEmpty();
}
@Test
public void accountPermissionsEnabledWithoutContractAddressMustError() {
parseCommand("--permissions-accounts-contract-enabled");
verifyZeroInteractions(mockRunnerBuilder);
assertThat(commandErrorOutput.toString())
.contains("No account permissioning contract address specified");
assertThat(commandOutput.toString()).isEmpty();
}
@Test
public void accountPermissionsEnabledWithInvalidContractAddressMustError() {
parseCommand(
"--permissions-accounts-contract-enabled",
"--permissions-accounts-contract-address",
"invalid-smart-contract-address");
verifyZeroInteractions(mockRunnerBuilder);
assertThat(commandErrorOutput.toString()).contains("Invalid value");
assertThat(commandOutput.toString()).isEmpty();
}
@Test
public void accountPermissionsEnabledWithTooShortContractAddressMustError() {
parseCommand(
"--permissions-accounts-contract-enabled",
"--permissions-accounts-contract-address",
"0x1234");
verifyZeroInteractions(mockRunnerBuilder);
assertThat(commandErrorOutput.toString()).contains("Invalid value");
assertThat(commandOutput.toString()).isEmpty();
}
@Test
public void accountPermissionsSmartContractMustUseOption() {
String smartContractAddress = "0x0000000000000000000000000000000000001234";
parseCommand(
"--permissions-accounts-contract-enabled",
"--permissions-accounts-contract-address",
smartContractAddress);
final SmartContractPermissioningConfiguration smartContractPermissioningConfiguration =
new SmartContractPermissioningConfiguration();
smartContractPermissioningConfiguration.setAccountSmartContractAddress(
Address.fromHexString(smartContractAddress));
smartContractPermissioningConfiguration.setSmartContractAccountWhitelistEnabled(true);
verify(mockController.getProtocolSchedule()).setTransactionFilter(any());
assertThat(commandErrorOutput.toString()).isEmpty();
assertThat(commandOutput.toString()).isEmpty();
}
@Test
public void nodePermissioningTomlPathWithoutOptionMustDisplayUsage() {
parseCommand("--permissions-nodes-config-file");
@ -549,12 +621,11 @@ public class PantheonCommandTest extends CommandTestAbstract {
Collections.singletonList("0x0000000000000000000000000000000000000009"));
verify(mockRunnerBuilder)
.permissioningConfiguration(permissioningConfigurationArgumentCaptor.capture());
verify(mockRunnerBuilder).build();
.accountPermissioningController(accountPermissioningControllerArgumentCaptor.capture());
PermissioningConfiguration config = permissioningConfigurationArgumentCaptor.getValue();
assertThat(config.getLocalConfig().get())
.isEqualToComparingFieldByField(localPermissioningConfiguration);
AccountPermissioningController controller =
accountPermissioningControllerArgumentCaptor.getValue();
assertThat(controller.getAccountLocalConfigPermissioningController()).isPresent();
assertThat(commandErrorOutput.toString()).isEmpty();
assertThat(commandOutput.toString()).isEmpty();

@ -84,6 +84,8 @@ permissions-accounts-config-file-enabled=false
permissions-accounts-config-file="./permissions_config.toml"
permissions-nodes-contract-enabled=false
permissions-nodes-contract-address="0x0000000000000000000000000000000000001234"
permissions-accounts-contract-enabled=false
permissions-accounts-contract-address="0x0000000000000000000000000000000000006789"
# Privacy
privacy-url="http://127.0.0.1:8888"

Loading…
Cancel
Save