Skip to content

Commit

Permalink
Introduce transaction validator interface (phase 1) (hyperledger#5673)
Browse files Browse the repository at this point in the history
Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>
  • Loading branch information
fab-10 authored Jul 7, 2023
1 parent 4b3853f commit 6ac03af
Show file tree
Hide file tree
Showing 23 changed files with 217 additions and 162 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolFactory;
import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions;
import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionValidator;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
import org.hyperledger.besu.ethereum.mainnet.TransactionValidator;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket;
import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage;
Expand Down Expand Up @@ -99,7 +99,7 @@ public class BesuEventsImplTest {
@Mock private EthContext mockEthContext;
@Mock private EthMessages mockEthMessages;
@Mock private EthScheduler mockEthScheduler;
@Mock private MainnetTransactionValidator mockTransactionValidator;
@Mock private TransactionValidator mockTransactionValidator;
@Mock private ProtocolSpec mockProtocolSpec;
@Mock private WorldStateArchive mockWorldStateArchive;
@Mock private MutableWorldState mockWorldState;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Difficulty;
import org.hyperledger.besu.ethereum.core.PermissionTransactionFilter;
import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader;
import org.hyperledger.besu.ethereum.core.TransactionFilter;
import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
Expand Down Expand Up @@ -216,12 +216,12 @@ public String listMilestones() {
/**
* Sets transaction filter.
*
* @param transactionFilter the transaction filter
* @param permissionTransactionFilter the transaction filter
*/
@Override
public void setTransactionFilter(final TransactionFilter transactionFilter) {
public void setTransactionFilter(final PermissionTransactionFilter permissionTransactionFilter) {
transitionUtils.dispatchConsumerAccordingToMergeState(
protocolSchedule -> protocolSchedule.setTransactionFilter(transactionFilter));
protocolSchedule -> protocolSchedule.setTransactionFilter(permissionTransactionFilter));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ public class EthGetTransactionReceiptTest {
null,
Optional.empty(),
null,
true,
true);
private final ProtocolSpec statusTransactionTypeSpec =
new ProtocolSpec(
Expand Down Expand Up @@ -144,6 +145,7 @@ public class EthGetTransactionReceiptTest {
null,
Optional.empty(),
null,
true,
true);

@SuppressWarnings("unchecked")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
import org.junit.runners.Parameterized;

@RunWith(Parameterized.class)
public class PendingTransactionFilterTest {
public class PendingPermissionTransactionFilterTest {

@Parameterized.Parameters
public static Collection<Object[]> data() {
Expand Down Expand Up @@ -97,7 +97,7 @@ public static Collection<Object[]> data() {
private final int limit;
private final List<String> expectedListOfTransactionHash;

public PendingTransactionFilterTest(
public PendingPermissionTransactionFilterTest(
final List<Filter> filters,
final int limit,
final List<String> expectedListOfTransactionHash) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@

import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand Down Expand Up @@ -78,15 +79,12 @@
*/
public class BlockTransactionSelector {

public record TransactionValidationResult(
Transaction transaction, ValidationResult<TransactionInvalidReason> validationResult) {}

public static class TransactionSelectionResults {
private final List<Transaction> transactions = Lists.newArrayList();
private final Map<TransactionType, List<Transaction>> transactionsByType =
new EnumMap<>(TransactionType.class);
private final List<TransactionReceipt> receipts = Lists.newArrayList();
private final List<TransactionValidationResult> invalidTransactions = Lists.newArrayList();
private final Map<Transaction, TransactionInvalidReason> invalidTransactions = new HashMap<>();
private final List<TransactionSelectionResult> selectionResults = Lists.newArrayList();
private long cumulativeGasUsed = 0;
private long cumulativeDataGasUsed = 0;
Expand Down Expand Up @@ -114,9 +112,8 @@ private void update(
}

private void updateWithInvalidTransaction(
final Transaction transaction,
final ValidationResult<TransactionInvalidReason> validationResult) {
invalidTransactions.add(new TransactionValidationResult(transaction, validationResult));
final Transaction transaction, final TransactionInvalidReason invalidReason) {
invalidTransactions.put(transaction, invalidReason);
}

public List<Transaction> getTransactions() {
Expand All @@ -139,7 +136,7 @@ public long getCumulativeDataGasUsed() {
return cumulativeDataGasUsed;
}

public List<TransactionValidationResult> getInvalidTransactions() {
public Map<Transaction, TransactionInvalidReason> getInvalidTransactions() {
return invalidTransactions;
}

Expand Down Expand Up @@ -278,7 +275,7 @@ public TransactionSelectionResults buildTransactionListForBlock() {
.log();
pendingTransactions.selectTransactions(
pendingTransaction -> {
final var res = evaluateTransaction(pendingTransaction, false);
final var res = evaluateTransaction(pendingTransaction);
transactionSelectionResults.addSelectionResult(res);
return res;
});
Expand All @@ -298,7 +295,7 @@ public TransactionSelectionResults buildTransactionListForBlock() {
public TransactionSelectionResults evaluateTransactions(final List<Transaction> transactions) {
transactions.forEach(
transaction ->
transactionSelectionResults.addSelectionResult(evaluateTransaction(transaction, true)));
transactionSelectionResults.addSelectionResult(evaluateTransaction(transaction)));
return transactionSelectionResults;
}

Expand All @@ -311,8 +308,7 @@ public TransactionSelectionResults evaluateTransactions(final List<Transaction>
* the space remaining in the block.
*
*/
private TransactionSelectionResult evaluateTransaction(
final Transaction transaction, final boolean reportFutureNonceTransactionsAsInvalid) {
private TransactionSelectionResult evaluateTransaction(final Transaction transaction) {
if (isCancelled.get()) {
throw new CancellationException("Cancelled during transaction selection.");
}
Expand Down Expand Up @@ -393,12 +389,9 @@ private TransactionSelectionResult evaluateTransaction(
}
return txSelectionResult;
} else {
transactionSelectionResults.updateWithInvalidTransaction(
transaction, effectiveResult.getValidationResult().getInvalidReason());

final boolean isIncorrectNonce = isIncorrectNonce(effectiveResult.getValidationResult());
if (!isIncorrectNonce || reportFutureNonceTransactionsAsInvalid) {
transactionSelectionResults.updateWithInvalidTransaction(
transaction, effectiveResult.getValidationResult());
}
return transactionSelectionResultForInvalidResult(
transaction, effectiveResult.getValidationResult());
}
Expand Down Expand Up @@ -472,10 +465,6 @@ private boolean isTransientValidationError(final TransactionInvalidReason invali
|| invalidReason.equals(TransactionInvalidReason.NONCE_TOO_HIGH);
}

private boolean isIncorrectNonce(final ValidationResult<TransactionInvalidReason> result) {
return result.getInvalidReason().equals(TransactionInvalidReason.NONCE_TOO_HIGH);
}

private boolean transactionTooLargeForBlock(final Transaction transaction) {
final long dataGasUsed = gasCalculator.dataGasCost(transaction.getBlobCount());

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* Copyright ConsenSys AG.
*
* Copyright Hyperledger Besu Contributors.
*
* 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
Expand All @@ -11,11 +12,12 @@
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.ethereum.core;

@FunctionalInterface
public interface TransactionFilter {
public interface PermissionTransactionFilter {
boolean permitted(
Transaction transaction, boolean checkLocalPermissions, boolean checkOnchainPermissions);
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public static ProtocolSpecBuilder tangerineWhistleDefinition(
final EvmConfiguration evmConfiguration) {
return MainnetProtocolSpecs.homesteadDefinition(
contractSizeLimit, configStackSizeLimit, evmConfiguration)
.isReplayProtectionSupported(true)
.gasCalculator(TangerineWhistleGasCalculator::new)
.transactionValidatorBuilder(
(gasCalculator, gasLimitCalculator) ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
import static com.google.common.base.Preconditions.checkArgument;

import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.PermissionTransactionFilter;
import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader;
import org.hyperledger.besu.ethereum.core.TransactionFilter;
import org.hyperledger.besu.ethereum.mainnet.ScheduledProtocolSpec.BlockNumberProtocolSpec;
import org.hyperledger.besu.ethereum.mainnet.ScheduledProtocolSpec.TimestampProtocolSpec;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
Expand Down Expand Up @@ -111,9 +111,12 @@ public boolean anyMatch(final Predicate<ScheduledProtocolSpec> predicate) {
}

@Override
public void setTransactionFilter(final TransactionFilter transactionFilter) {
public void setTransactionFilter(final PermissionTransactionFilter permissionTransactionFilter) {
protocolSpecs.forEach(
spec -> spec.spec().getTransactionValidator().setTransactionFilter(transactionFilter));
spec ->
spec.spec()
.getTransactionValidator()
.setPermissionTransactionFilter(permissionTransactionFilter));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ public static ProtocolSpecBuilder spuriousDragonDefinition(
final int stackSizeLimit = configStackSizeLimit.orElse(MessageFrame.DEFAULT_MAX_STACK_SIZE);

return tangerineWhistleDefinition(OptionalInt.empty(), configStackSizeLimit, evmConfiguration)
.isReplayProtectionSupported(true)
.gasCalculator(SpuriousDragonGasCalculator::new)
.skipZeroBlockRewards(true)
.messageCallProcessorBuilder(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public class MainnetTransactionProcessor {

protected final GasCalculator gasCalculator;

protected final MainnetTransactionValidator transactionValidator;
protected final TransactionValidator transactionValidator;

private final AbstractMessageProcessor contractCreationProcessor;

Expand All @@ -81,7 +81,7 @@ public class MainnetTransactionProcessor {

public MainnetTransactionProcessor(
final GasCalculator gasCalculator,
final MainnetTransactionValidator transactionValidator,
final TransactionValidator transactionValidator,
final AbstractMessageProcessor contractCreationProcessor,
final AbstractMessageProcessor messageCallProcessor,
final boolean clearEmptyAccounts,
Expand Down Expand Up @@ -498,7 +498,7 @@ public TransactionProcessingResult processTransaction(
}
}

public MainnetTransactionValidator getTransactionValidator() {
public TransactionValidator getTransactionValidator() {
return transactionValidator;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.GasLimitCalculator;
import org.hyperledger.besu.ethereum.core.PermissionTransactionFilter;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.TransactionFilter;
import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket;
import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason;
import org.hyperledger.besu.evm.account.Account;
Expand All @@ -39,7 +39,7 @@
* <p>The {@link MainnetTransactionValidator} performs the intrinsic gas cost check on the given
* {@link Transaction}.
*/
public class MainnetTransactionValidator {
public class MainnetTransactionValidator implements TransactionValidator {

private final GasCalculator gasCalculator;
private final GasLimitCalculator gasLimitCalculator;
Expand All @@ -49,7 +49,7 @@ public class MainnetTransactionValidator {

private final Optional<BigInteger> chainId;

private Optional<TransactionFilter> transactionFilter = Optional.empty();
private Optional<PermissionTransactionFilter> permissionTransactionFilter = Optional.empty();
private final Set<TransactionType> acceptedTransactionTypes;

private final int maxInitcodeSize;
Expand Down Expand Up @@ -100,16 +100,7 @@ public MainnetTransactionValidator(
this.maxInitcodeSize = maxInitcodeSize;
}

/**
* Asserts whether a transaction is valid.
*
* @param transaction the transaction to validate
* @param baseFee optional baseFee
* @param transactionValidationParams Validation parameters that will be used
* @return An empty {@link Optional} if the transaction is considered valid; otherwise an {@code
* Optional} containing a {@link TransactionInvalidReason} that identifies why the transaction
* is invalid.
*/
@Override
public ValidationResult<TransactionInvalidReason> validate(
final Transaction transaction,
final Optional<Wei> baseFee,
Expand Down Expand Up @@ -199,6 +190,7 @@ private ValidationResult<TransactionInvalidReason> validateCostAndFee(
return ValidationResult.valid();
}

@Override
public ValidationResult<TransactionInvalidReason> validateForSender(
final Transaction transaction,
final Account sender,
Expand Down Expand Up @@ -256,11 +248,7 @@ public ValidationResult<TransactionInvalidReason> validateForSender(
return ValidationResult.valid();
}

public boolean isReplayProtectionSupported() {
return chainId.isPresent();
}

public ValidationResult<TransactionInvalidReason> validateTransactionSignature(
private ValidationResult<TransactionInvalidReason> validateTransactionSignature(
final Transaction transaction) {
if (chainId.isPresent()
&& (transaction.getChainId().isPresent() && !transaction.getChainId().equals(chainId))) {
Expand Down Expand Up @@ -302,7 +290,7 @@ public ValidationResult<TransactionInvalidReason> validateTransactionSignature(
private boolean isSenderAllowed(
final Transaction transaction, final TransactionValidationParams validationParams) {
if (validationParams.checkLocalPermissions() || validationParams.checkOnchainPermissions()) {
return transactionFilter
return permissionTransactionFilter
.map(
c ->
c.permitted(
Expand All @@ -315,30 +303,9 @@ private boolean isSenderAllowed(
}
}

public void setTransactionFilter(final TransactionFilter transactionFilter) {
this.transactionFilter = Optional.of(transactionFilter);
}

/**
* Asserts whether a transaction is valid for the sender account's current state.
*
* <p>Note: {@code validate} should be called before getting the sender {@link Account} used in
* this method to ensure that a sender can be extracted from the {@link Transaction}.
*
* @param transaction the transaction to validateMessageFrame.State.COMPLETED_FAILED
* @param sender the sender account state to validate against
* @param allowFutureNonce if true, transactions with nonce equal or higher than the account nonce
* will be considered valid (used when received transactions in the transaction pool). If
* false, only a transaction with the nonce equals the account nonce will be considered valid
* (used when processing transactions).
* @return An empty {@link Optional} if the transaction is considered valid; otherwise an {@code
* Optional} containing a {@link TransactionInvalidReason} that identifies why the transaction
* is invalid.
*/
public ValidationResult<TransactionInvalidReason> validateForSender(
final Transaction transaction, final Account sender, final boolean allowFutureNonce) {
final TransactionValidationParams validationParams =
ImmutableTransactionValidationParams.builder().isAllowFutureNonce(allowFutureNonce).build();
return validateForSender(transaction, sender, validationParams);
@Override
public void setPermissionTransactionFilter(
final PermissionTransactionFilter permissionTransactionFilter) {
this.permissionTransactionFilter = Optional.of(permissionTransactionFilter);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@

package org.hyperledger.besu.ethereum.mainnet;

import org.hyperledger.besu.ethereum.core.TransactionFilter;
import org.hyperledger.besu.ethereum.core.PermissionTransactionFilter;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;

public interface PrivacySupportingProtocolSchedule {

void setTransactionFilter(final TransactionFilter transactionFilter);
void setTransactionFilter(final PermissionTransactionFilter permissionTransactionFilter);

void setPublicWorldStateArchiveForPrivacyBlockProcessor(
final WorldStateArchive publicWorldStateArchive);
Expand Down
Loading

0 comments on commit 6ac03af

Please sign in to comment.