Skip to content

Commit

Permalink
In the txpool, reject a tx if its simulation fails (#2)
Browse files Browse the repository at this point in the history
Signed-off-by: Gabriel-Trintinalia <gabriel.trintinalia@consensys.net>
Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>
Co-authored-by: Gabriel-Trintinalia <gabriel.trintinalia@consensys.net>
  • Loading branch information
fab-10 and Gabriel-Trintinalia authored Apr 11, 2024
1 parent 0be75a3 commit 8e3fce7
Showing 14 changed files with 540 additions and 91 deletions.
14 changes: 8 additions & 6 deletions PLUGINS.md
Original file line number Diff line number Diff line change
@@ -44,12 +44,14 @@ of a transaction.

#### CLI Options

| Option Name | Default Value | Command Line Argument |
|----------------------------------|----------------------|---------------------------------------------------|
| MAX_BLOCK_CALLDATA_SIZE | 70000 | `--plugin-linea-max-block-calldata-size` |
| MODULE_LIMIT_FILE_PATH | moduleLimitFile.toml | `--plugin-linea-module-limit-file-path` |
| OVER_LINE_COUNT_LIMIT_CACHE_SIZE | 10_000 | `--plugin-linea-over-line-count-limit-cache-size` |
| MAX_GAS_PER_BLOCK | 30_000_000L | `--plugin-linea-max-block-gas` |
| Option Name | Default Value | Command Line Argument |
|-------------------------------------|----------------------|-------------------------------------------------------|
| MAX_BLOCK_CALLDATA_SIZE | 70000 | `--plugin-linea-max-block-calldata-size` |
| MODULE_LIMIT_FILE_PATH | moduleLimitFile.toml | `--plugin-linea-module-limit-file-path` |
| OVER_LINE_COUNT_LIMIT_CACHE_SIZE | 10_000 | `--plugin-linea-over-line-count-limit-cache-size` |
| MAX_GAS_PER_BLOCK | 30_000_000L | `--plugin-linea-max-block-gas` |
| TX_POOL_ENABLE_SIMULATION_CHECK_API | true | `--plugin-linea-tx-pool-simulation-check-api-enabled` |
| TX_POOL_ENABLE_SIMULATION_CHECK_P2P | false | `--plugin-linea-tx-pool-simulation-check-p2p-enabled` |


### Transaction validation - LineaTransactionValidatorPlugin
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -149,7 +150,8 @@ private void assertTransactionsInCorrectBlocks(Web3j web3j, List<String> hashes,
protected SimpleStorage deploySimpleStorage() throws Exception {
final Web3j web3j = minerNode.nodeRequests().eth();
final Credentials credentials = Credentials.create(Accounts.GENESIS_ACCOUNT_ONE_PRIVATE_KEY);
TransactionManager txManager = new RawTransactionManager(web3j, credentials, CHAIN_ID);
TransactionManager txManager =
new RawTransactionManager(web3j, credentials, CHAIN_ID, createReceiptProcessor(web3j));

final RemoteCall<SimpleStorage> deploy =
SimpleStorage.deploy(web3j, txManager, new DefaultGasProvider());
@@ -198,11 +200,15 @@ protected void assertTransactionNotInThePool(String hash) {
.notInTransactionPool(Hash.fromHexString(hash)));
}

protected List<Map<String, String>> getTxPoolContent() {
return minerNode.execute(new TxPoolTransactions().getTxPoolContents());
}

private TransactionReceiptProcessor createReceiptProcessor(Web3j web3j) {
return new PollingTransactionReceiptProcessor(
web3j,
TransactionManager.DEFAULT_POLLING_FREQUENCY,
TransactionManager.DEFAULT_POLLING_ATTEMPTS_PER_TX_HASH);
Math.max(1000, LINEA_CLIQUE_OPTIONS.blockPeriodSeconds() * 1000 / 5),
LINEA_CLIQUE_OPTIONS.blockPeriodSeconds() * 3);
}

protected String sendTransactionWithGivenLengthPayload(
Original file line number Diff line number Diff line change
@@ -43,6 +43,7 @@ public List<String> getTestCliOptions() {
.set(
"--plugin-linea-module-limit-file-path=",
getResourcePath("/txOverflowModuleLimits.toml"))
.set("--plugin-linea-tx-pool-simulation-check-api-enabled=", "false")
.build();
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright Consensys Software Inc.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package linea.plugin.acc.test.rpc.linea;

import static org.assertj.core.api.Assertions.assertThat;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

import linea.plugin.acc.test.LineaPluginTestBase;
import linea.plugin.acc.test.TestCommandLineOptionsBuilder;
import linea.plugin.acc.test.tests.web3j.generated.SimpleStorage;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.tests.acceptance.dsl.account.Account;
import org.hyperledger.besu.tests.acceptance.dsl.account.Accounts;
import org.junit.jupiter.api.Test;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.TransactionEncoder;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.methods.response.EthSendTransaction;
import org.web3j.tx.gas.DefaultGasProvider;
import org.web3j.utils.Numeric;

public class EthSendRawTransactionSimulationCheckTest extends LineaPluginTestBase {

private static final BigInteger GAS_LIMIT = DefaultGasProvider.GAS_LIMIT;
private static final BigInteger VALUE = BigInteger.ZERO;
private static final BigInteger GAS_PRICE = BigInteger.TEN.pow(9);

@Override
public List<String> getTestCliOptions() {
return new TestCommandLineOptionsBuilder()
.set(
"--plugin-linea-module-limit-file-path=",
getResourcePath("/txOverflowModuleLimits.toml"))
.set("--plugin-linea-tx-pool-simulation-check-api-enabled=", "true")
.build();
}

@Test
public void transactionOverModuleLineCountNotAccepted() throws Exception {
final SimpleStorage simpleStorage = deploySimpleStorage();
final Web3j web3j = minerNode.nodeRequests().eth();
final String contractAddress = simpleStorage.getContractAddress();
final String txData = simpleStorage.add(BigInteger.valueOf(100)).encodeFunctionCall();

// this tx will not be accepted since it goes above the line count limit
final RawTransaction txModuleLineCountTooBig =
RawTransaction.createTransaction(
CHAIN_ID,
BigInteger.valueOf(1),
GAS_LIMIT.divide(BigInteger.TEN),
contractAddress,
VALUE,
txData,
GAS_PRICE,
GAS_PRICE.multiply(BigInteger.TEN).add(BigInteger.ONE));
final byte[] signedTxContractInteraction =
TransactionEncoder.signMessage(
txModuleLineCountTooBig, Credentials.create(Accounts.GENESIS_ACCOUNT_TWO_PRIVATE_KEY));

final EthSendTransaction signedTxContractInteractionResp =
web3j.ethSendRawTransaction(Numeric.toHexString(signedTxContractInteraction)).send();

assertThat(signedTxContractInteractionResp.hasError()).isTrue();
assertThat(signedTxContractInteractionResp.getError().getMessage())
.isEqualTo("Transaction line count for module ADD=2018 is above the limit 70");

assertThat(getTxPoolContent()).isEmpty();

// these are under the line count limit and should be accepted and selected
final Account fewLinesSender = accounts.getSecondaryBenefactor();
final Account recipient = accounts.createAccount("recipient");
final List<Hash> expectedConfirmedTxs = new ArrayList<>(4);

expectedConfirmedTxs.addAll(
minerNode.execute(
accountTransactions.createIncrementalTransfers(fewLinesSender, recipient, 4)));

final var txPoolContentByHash = getTxPoolContent().stream().map(e -> e.get("hash")).toList();
assertThat(txPoolContentByHash)
.containsExactlyInAnyOrderElementsOf(
expectedConfirmedTxs.stream().map(Hash::toHexString).toList());

expectedConfirmedTxs.stream()
.map(Hash::toHexString)
.forEach(hash -> minerNode.verify(eth.expectSuccessfulTransactionReceipt(hash)));
}
}
Original file line number Diff line number Diff line change
@@ -23,8 +23,8 @@
import net.consensys.linea.config.LineaProfitabilityConfiguration;
import net.consensys.linea.config.LineaRpcCliOptions;
import net.consensys.linea.config.LineaRpcConfiguration;
import net.consensys.linea.config.LineaTracerCliOptions;
import net.consensys.linea.config.LineaTracerConfiguration;
import net.consensys.linea.config.LineaTracerConfigurationCLiOptions;
import net.consensys.linea.config.LineaTransactionPoolValidatorCliOptions;
import net.consensys.linea.config.LineaTransactionPoolValidatorConfiguration;
import net.consensys.linea.config.LineaTransactionSelectorCliOptions;
@@ -44,7 +44,7 @@ public abstract class AbstractLineaSharedOptionsPlugin implements BesuPlugin {
private static LineaRpcCliOptions rpcCliOptions;
private static LineaProfitabilityCliOptions profitabilityCliOptions;
protected static LineaTransactionSelectorConfiguration transactionSelectorConfiguration;
protected static LineaTracerConfigurationCLiOptions tracerConfigurationCliOptions;
protected static LineaTracerCliOptions tracerCliOptions;
protected static LineaTransactionPoolValidatorConfiguration transactionPoolValidatorConfiguration;
protected static LineaL1L2BridgeConfiguration l1L2BridgeConfiguration;
protected static LineaRpcConfiguration rpcConfiguration;
@@ -71,14 +71,14 @@ public synchronized void register(final BesuContext context) {
l1L2BridgeCliOptions = LineaL1L2BridgeCliOptions.create();
rpcCliOptions = LineaRpcCliOptions.create();
profitabilityCliOptions = LineaProfitabilityCliOptions.create();
tracerConfigurationCliOptions = LineaTracerConfigurationCLiOptions.create();
tracerCliOptions = LineaTracerCliOptions.create();

cmdlineOptions.addPicoCLIOptions(CLI_OPTIONS_PREFIX, transactionSelectorCliOptions);
cmdlineOptions.addPicoCLIOptions(CLI_OPTIONS_PREFIX, transactionPoolValidatorCliOptions);
cmdlineOptions.addPicoCLIOptions(CLI_OPTIONS_PREFIX, l1L2BridgeCliOptions);
cmdlineOptions.addPicoCLIOptions(CLI_OPTIONS_PREFIX, rpcCliOptions);
cmdlineOptions.addPicoCLIOptions(CLI_OPTIONS_PREFIX, profitabilityCliOptions);
cmdlineOptions.addPicoCLIOptions(CLI_OPTIONS_PREFIX, tracerConfigurationCliOptions);
cmdlineOptions.addPicoCLIOptions(CLI_OPTIONS_PREFIX, tracerCliOptions);
cliOptionsRegistered = true;
}
}
@@ -91,7 +91,7 @@ public void beforeExternalServices() {
l1L2BridgeConfiguration = l1L2BridgeCliOptions.toDomainObject();
rpcConfiguration = rpcCliOptions.toDomainObject();
profitabilityConfiguration = profitabilityCliOptions.toDomainObject();
tracerConfiguration = tracerConfigurationCliOptions.toDomainObject();
tracerConfiguration = tracerCliOptions.toDomainObject();
configured = true;
}

Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@
import com.google.common.base.MoreObjects;
import picocli.CommandLine;

public class LineaTracerConfigurationCLiOptions {
public class LineaTracerCliOptions {

public static final String MODULE_LIMIT_FILE_PATH = "--plugin-linea-module-limit-file-path";
public static final String DEFAULT_MODULE_LIMIT_FILE_PATH = "moduleLimitFile.toml";
@@ -30,15 +30,15 @@ public class LineaTracerConfigurationCLiOptions {
"Path to the toml file containing the module limits (default: ${DEFAULT-VALUE})")
private String moduleLimitFilePath = DEFAULT_MODULE_LIMIT_FILE_PATH;

private LineaTracerConfigurationCLiOptions() {}
private LineaTracerCliOptions() {}

/**
* Create Linea cli options.
*
* @return the Linea cli options
*/
public static LineaTracerConfigurationCLiOptions create() {
return new LineaTracerConfigurationCLiOptions();
public static LineaTracerCliOptions create() {
return new LineaTracerCliOptions();
}

/**
@@ -47,9 +47,8 @@ public static LineaTracerConfigurationCLiOptions create() {
* @param config the config
* @return the Linea cli options
*/
public static LineaTracerConfigurationCLiOptions fromConfig(
final LineaTracerConfiguration config) {
final LineaTracerConfigurationCLiOptions options = create();
public static LineaTracerCliOptions fromConfig(final LineaTracerConfiguration config) {
final LineaTracerCliOptions options = create();
options.moduleLimitFilePath = config.moduleLimitsFilePath();
return options;
}
Original file line number Diff line number Diff line change
@@ -30,6 +30,14 @@ public class LineaTransactionPoolValidatorCliOptions {
public static final String MAX_TX_CALLDATA_SIZE = "--plugin-linea-max-tx-calldata-size";
public static final int DEFAULT_MAX_TX_CALLDATA_SIZE = 60_000;

public static final String TX_POOL_ENABLE_SIMULATION_CHECK_API =
"--plugin-linea-tx-pool-simulation-check-api-enabled";
public static final boolean DEFAULT_TX_POOL_ENABLE_SIMULATION_CHECK_API = true;

public static final String TX_POOL_ENABLE_SIMULATION_CHECK_P2P =
"--plugin-linea-tx-pool-simulation-check-p2p-enabled";
public static final boolean DEFAULT_TX_POOL_ENABLE_SIMULATION_CHECK_P2P = false;

@CommandLine.Option(
names = {DENY_LIST_PATH},
hidden = true,
@@ -58,6 +66,24 @@ public class LineaTransactionPoolValidatorCliOptions {
+ ")")
private int maxTxCallDataSize = DEFAULT_MAX_TX_CALLDATA_SIZE;

@CommandLine.Option(
names = {TX_POOL_ENABLE_SIMULATION_CHECK_API},
arity = "0..1",
hidden = true,
paramLabel = "<BOOLEAN>",
description =
"Enable the simulation check for txs received via API? (default: ${DEFAULT-VALUE})")
private boolean txPoolSimulationCheckApiEnabled = DEFAULT_TX_POOL_ENABLE_SIMULATION_CHECK_API;

@CommandLine.Option(
names = {TX_POOL_ENABLE_SIMULATION_CHECK_P2P},
arity = "0..1",
hidden = true,
paramLabel = "<BOOLEAN>",
description =
"Enable the simulation check for txs received via p2p? (default: ${DEFAULT-VALUE})")
private boolean txPoolSimulationCheckP2pEnabled = DEFAULT_TX_POOL_ENABLE_SIMULATION_CHECK_P2P;

private LineaTransactionPoolValidatorCliOptions() {}

/**
@@ -81,7 +107,8 @@ public static LineaTransactionPoolValidatorCliOptions fromConfig(
options.denyListPath = config.denyListPath();
options.maxTxGasLimit = config.maxTxGasLimit();
options.maxTxCallDataSize = config.maxTxCalldataSize();

options.txPoolSimulationCheckApiEnabled = config.txPoolSimulationCheckApiEnabled();
options.txPoolSimulationCheckP2pEnabled = config.txPoolSimulationCheckP2pEnabled();
return options;
}

@@ -92,7 +119,11 @@ public static LineaTransactionPoolValidatorCliOptions fromConfig(
*/
public LineaTransactionPoolValidatorConfiguration toDomainObject() {
return new LineaTransactionPoolValidatorConfiguration(
denyListPath, maxTxGasLimit, maxTxCallDataSize);
denyListPath,
maxTxGasLimit,
maxTxCallDataSize,
txPoolSimulationCheckApiEnabled,
txPoolSimulationCheckP2pEnabled);
}

@Override
@@ -101,6 +132,8 @@ public String toString() {
.add(DENY_LIST_PATH, denyListPath)
.add(MAX_TX_GAS_LIMIT_OPTION, maxTxGasLimit)
.add(MAX_TX_CALLDATA_SIZE, maxTxCallDataSize)
.add(TX_POOL_ENABLE_SIMULATION_CHECK_API, txPoolSimulationCheckApiEnabled)
.add(TX_POOL_ENABLE_SIMULATION_CHECK_P2P, txPoolSimulationCheckP2pEnabled)
.toString();
}
}
Original file line number Diff line number Diff line change
@@ -26,4 +26,8 @@
*/
@Builder(toBuilder = true)
public record LineaTransactionPoolValidatorConfiguration(
String denyListPath, int maxTxGasLimit, int maxTxCalldataSize) {}
String denyListPath,
int maxTxGasLimit,
int maxTxCalldataSize,
boolean txPoolSimulationCheckApiEnabled,
boolean txPoolSimulationCheckP2pEnabled) {}
Original file line number Diff line number Diff line change
@@ -49,6 +49,7 @@
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.evm.tracing.EstimateGasOperationTracer;
import org.hyperledger.besu.plugin.data.BlockHeader;
import org.hyperledger.besu.plugin.services.BesuConfiguration;
import org.hyperledger.besu.plugin.services.BlockchainService;
import org.hyperledger.besu.plugin.services.TransactionSimulationService;
@@ -209,11 +210,12 @@ private Long estimateGasUsed(
final Wei minGasPrice) {

final var estimateGasOperationTracer = new EstimateGasOperationTracer();
final var zkTracer = createZkTracer();
final var chainHeadHeader = blockchainService.getChainHeadHeader();
final var zkTracer = createZkTracer(chainHeadHeader);
TracerAggregator tracerAggregator =
TracerAggregator.create(estimateGasOperationTracer, zkTracer);

final var chainHeadHash = blockchainService.getChainHeadHash();
final var chainHeadHash = chainHeadHeader.getBlockHash();
final var maybeSimulationResults =
transactionSimulationService.simulate(transaction, chainHeadHash, tracerAggregator, true);

@@ -417,10 +419,10 @@ private Transaction createTransactionForSimulation(
return txBuilder.build();
}

private ZkTracer createZkTracer() {
private ZkTracer createZkTracer(final BlockHeader chainHeadHeader) {
var zkTracer = new ZkTracer(l1L2BridgeConfiguration);
zkTracer.traceStartConflation(1L);
zkTracer.traceStartBlock(blockchainService.getChainHeadHeader());
zkTracer.traceStartBlock(chainHeadHeader);
return zkTracer;
}

@@ -443,6 +445,11 @@ private void handleModuleOverLimit(ModuleLimitsValidationResult moduleLimitResul
log.warn(txOverflowMsg);
throw new PluginRpcEndpointException(new TransactionSimulationError(txOverflowMsg));
}

final String internalErrorMsg =
String.format("Do not know what to do with result %s", moduleLimitResult.getResult());
log.error(internalErrorMsg);
throw new PluginRpcEndpointException(RpcErrorType.PLUGIN_INTERNAL_ERROR, internalErrorMsg);
}

public record Response(
Loading

0 comments on commit 8e3fce7

Please sign in to comment.