Skip to content

Commit

Permalink
PAN-2445: Onchain account permissioning (PegaSysEng#1507)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucassaldanha authored and iikirilov committed Jun 8, 2019
1 parent a772bf3 commit e511d37
Show file tree
Hide file tree
Showing 16 changed files with 339 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ private boolean isSenderAllowed(final Transaction transaction, final boolean isS
return transactionFilter.map(c -> c.permitted(transaction, isStateChange)).orElse(true);
}

@Override
public void setTransactionFilter(final TransactionFilter transactionFilter) {
this.transactionFilter = Optional.of(transactionFilter);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

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;
Expand Down Expand Up @@ -69,4 +71,9 @@ public String listMilestones() {
.map(spec -> spec.getSpec().getName() + ": " + spec.getBlock())
.collect(Collectors.joining(", ", "[", "]"));
}

@Override
public void setTransactionFilter(final TransactionFilter transactionFilter) {
protocolSpecs.forEach(spec -> spec.getSpec().setTransactionFilter(transactionFilter));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -20,4 +22,6 @@ public interface ProtocolSchedule<C> {
ProtocolSpec<C> getByBlockNumber(long number);

Optional<BigInteger> getChainId();

void setTransactionFilter(TransactionFilter transactionFilter);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
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;
Expand Down Expand Up @@ -243,4 +244,8 @@ public MiningBeneficiaryCalculator getMiningBeneficiaryCalculator() {
public PrecompileContractRegistry getPrecompileContractRegistry() {
return precompileContractRegistry;
}

public void setTransactionFilter(final TransactionFilter transactionFilter) {
transactionValidator.setTransactionFilter(transactionFilter);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

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 {
Expand Down Expand Up @@ -54,6 +55,8 @@ default ValidationResult<TransactionInvalidReason> validateForSender(
ValidationResult<TransactionInvalidReason> validateForSender(
Transaction transaction, Account sender, TransactionValidationParams validationParams);

void setTransactionFilter(TransactionFilter transactionFilter);

enum TransactionInvalidReason {
WRONG_CHAIN_ID,
REPLAY_PROTECTED_SIGNATURES_NOT_SUPPORTED,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ public void transactionWithNullSenderCanBeValidIfGasPriceAndValueIsZero() {
public void shouldRejectTransactionIfAccountIsNotPermitted() {
final MainnetTransactionValidator validator =
new MainnetTransactionValidator(gasCalculator, false, Optional.empty());

validator.setTransactionFilter(transactionFilter(false));

assertThat(validator.validateForSender(basicTransaction, accountWithNonce(0), true))
Expand All @@ -169,7 +168,6 @@ public void shouldRejectTransactionIfAccountIsNotPermitted() {
public void shouldAcceptValidTransactionIfAccountIsPermitted() {
final MainnetTransactionValidator validator =
new MainnetTransactionValidator(gasCalculator, false, Optional.empty());

validator.setTransactionFilter(transactionFilter(true));

assertThat(validator.validateForSender(basicTransaction, accountWithNonce(0), true))
Expand All @@ -178,13 +176,13 @@ public void shouldAcceptValidTransactionIfAccountIsPermitted() {

@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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -232,17 +233,32 @@ private List<String> normalizeAccounts(final List<String> accounts) {

@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;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,18 @@

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;

Expand Down Expand Up @@ -101,6 +107,11 @@ public 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 =
Expand Down Expand Up @@ -131,9 +142,17 @@ public boolean isPermitted(final Transaction transaction) {

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;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,69 @@
*/
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 =
transactionSmartContractPermissioningController;
}

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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
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;
Expand All @@ -37,6 +38,7 @@
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;
Expand Down Expand Up @@ -97,6 +99,8 @@ private Transaction transactionForAccount(final Address address) {
.gasPrice(Wei.ZERO)
.gasLimit(0)
.payload(BytesValue.EMPTY)
.nonce(1)
.signature(Signature.create(BigInteger.ONE, BigInteger.TEN, (byte) 1))
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
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;
Expand All @@ -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
Expand Down
Loading

0 comments on commit e511d37

Please sign in to comment.