From 52b6531d9f5bbddab322a3bebf54ece77eb066ed Mon Sep 17 00:00:00 2001 From: Fabio Di Fabio Date: Tue, 9 Apr 2024 20:03:36 +0200 Subject: [PATCH] Reject a tx, sent via eth_sendRawTransaction, if its simulation fails Signed-off-by: Fabio Di Fabio --- .../linea/rpc/linea/LineaEstimateGas.java | 15 +- .../LineaTransactionPoolValidatorFactory.java | 24 ++- .../LineaTransactionPoolValidatorPlugin.java | 19 ++- .../validators/SimulationValidator.java | 116 +++++++++++++ .../validators/SimulationValidatorTest.java | 159 ++++++++++++++++++ ...TraceLineLimitTransactionSelectorTest.java | 68 +++++--- 6 files changed, 369 insertions(+), 32 deletions(-) create mode 100644 arithmetization/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/validators/SimulationValidator.java create mode 100644 arithmetization/src/test/java/net/consensys/linea/sequencer/txpoolvalidation/validators/SimulationValidatorTest.java diff --git a/arithmetization/src/main/java/net/consensys/linea/rpc/linea/LineaEstimateGas.java b/arithmetization/src/main/java/net/consensys/linea/rpc/linea/LineaEstimateGas.java index 4e9b4707..e5c38813 100644 --- a/arithmetization/src/main/java/net/consensys/linea/rpc/linea/LineaEstimateGas.java +++ b/arithmetization/src/main/java/net/consensys/linea/rpc/linea/LineaEstimateGas.java @@ -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( diff --git a/arithmetization/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/LineaTransactionPoolValidatorFactory.java b/arithmetization/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/LineaTransactionPoolValidatorFactory.java index 6fabee4e..99746546 100644 --- a/arithmetization/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/LineaTransactionPoolValidatorFactory.java +++ b/arithmetization/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/LineaTransactionPoolValidatorFactory.java @@ -16,18 +16,22 @@ package net.consensys.linea.sequencer.txpoolvalidation; import java.util.Arrays; +import java.util.Map; import java.util.Optional; import java.util.Set; +import net.consensys.linea.config.LineaL1L2BridgeConfiguration; import net.consensys.linea.config.LineaProfitabilityConfiguration; import net.consensys.linea.config.LineaTransactionPoolValidatorConfiguration; import net.consensys.linea.sequencer.txpoolvalidation.validators.AllowedAddressValidator; import net.consensys.linea.sequencer.txpoolvalidation.validators.CalldataValidator; import net.consensys.linea.sequencer.txpoolvalidation.validators.GasLimitValidator; import net.consensys.linea.sequencer.txpoolvalidation.validators.ProfitabilityValidator; +import net.consensys.linea.sequencer.txpoolvalidation.validators.SimulationValidator; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.plugin.services.BesuConfiguration; import org.hyperledger.besu.plugin.services.BlockchainService; +import org.hyperledger.besu.plugin.services.TransactionSimulationService; import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionPoolValidator; import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionPoolValidatorFactory; @@ -36,21 +40,30 @@ public class LineaTransactionPoolValidatorFactory implements PluginTransactionPo private final BesuConfiguration besuConfiguration; private final BlockchainService blockchainService; + private final TransactionSimulationService transactionSimulationService; private final LineaTransactionPoolValidatorConfiguration txPoolValidatorConf; private final LineaProfitabilityConfiguration profitabilityConf; private final Set
denied; + private final Map moduleLineLimitsMap; + private final LineaL1L2BridgeConfiguration l1L2BridgeConfiguration; public LineaTransactionPoolValidatorFactory( final BesuConfiguration besuConfiguration, final BlockchainService blockchainService, + final TransactionSimulationService transactionSimulationService, final LineaTransactionPoolValidatorConfiguration txPoolValidatorConf, final LineaProfitabilityConfiguration profitabilityConf, - final Set
denied) { + final Set
deniedAddresses, + final Map moduleLineLimitsMap, + final LineaL1L2BridgeConfiguration l1L2BridgeConfiguration) { this.besuConfiguration = besuConfiguration; this.blockchainService = blockchainService; + this.transactionSimulationService = transactionSimulationService; this.txPoolValidatorConf = txPoolValidatorConf; this.profitabilityConf = profitabilityConf; - this.denied = denied; + this.denied = deniedAddresses; + this.moduleLineLimitsMap = moduleLineLimitsMap; + this.l1L2BridgeConfiguration = l1L2BridgeConfiguration; } @Override @@ -60,7 +73,12 @@ public PluginTransactionPoolValidator createTransactionValidator() { new AllowedAddressValidator(denied), new GasLimitValidator(txPoolValidatorConf), new CalldataValidator(txPoolValidatorConf), - new ProfitabilityValidator(besuConfiguration, blockchainService, profitabilityConf) + new ProfitabilityValidator(besuConfiguration, blockchainService, profitabilityConf), + new SimulationValidator( + blockchainService, + transactionSimulationService, + moduleLineLimitsMap, + l1L2BridgeConfiguration) }; return (transaction, isLocal, hasPriority) -> diff --git a/arithmetization/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/LineaTransactionPoolValidatorPlugin.java b/arithmetization/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/LineaTransactionPoolValidatorPlugin.java index 52eb2de9..4d255cb5 100644 --- a/arithmetization/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/LineaTransactionPoolValidatorPlugin.java +++ b/arithmetization/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/LineaTransactionPoolValidatorPlugin.java @@ -15,6 +15,8 @@ package net.consensys.linea.sequencer.txpoolvalidation; +import static net.consensys.linea.sequencer.modulelimit.ModuleLineCountValidator.createLimitModules; + import java.io.File; import java.nio.file.Files; import java.nio.file.Path; @@ -32,6 +34,7 @@ import org.hyperledger.besu.plugin.services.BesuConfiguration; import org.hyperledger.besu.plugin.services.BlockchainService; import org.hyperledger.besu.plugin.services.TransactionPoolValidatorService; +import org.hyperledger.besu.plugin.services.TransactionSimulationService; /** * This class extends the default transaction validation rules for adding transactions to the @@ -46,6 +49,7 @@ public class LineaTransactionPoolValidatorPlugin extends AbstractLineaRequiredPl private BesuConfiguration besuConfiguration; private BlockchainService blockchainService; private TransactionPoolValidatorService transactionPoolValidatorService; + private TransactionSimulationService transactionSimulationService; @Override public Optional getName() { @@ -77,6 +81,14 @@ public void doRegister(final BesuContext context) { () -> new RuntimeException( "Failed to obtain TransactionPoolValidationService from the BesuContext.")); + + transactionSimulationService = + context + .getService(TransactionSimulationService.class) + .orElseThrow( + () -> + new RuntimeException( + "Failed to obtain TransactionSimulatorService from the BesuContext.")); } @Override @@ -85,16 +97,19 @@ public void beforeExternalServices() { try (Stream lines = Files.lines( Path.of(new File(transactionPoolValidatorConfiguration.denyListPath()).toURI()))) { - final Set
denied = + final Set
deniedAddresses = lines.map(l -> Address.fromHexString(l.trim())).collect(Collectors.toUnmodifiableSet()); transactionPoolValidatorService.registerPluginTransactionValidatorFactory( new LineaTransactionPoolValidatorFactory( besuConfiguration, blockchainService, + transactionSimulationService, transactionPoolValidatorConfiguration, profitabilityConfiguration, - denied)); + deniedAddresses, + createLimitModules(tracerConfiguration), + l1L2BridgeConfiguration)); } catch (Exception e) { throw new RuntimeException(e); diff --git a/arithmetization/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/validators/SimulationValidator.java b/arithmetization/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/validators/SimulationValidator.java new file mode 100644 index 00000000..f011e4ea --- /dev/null +++ b/arithmetization/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/validators/SimulationValidator.java @@ -0,0 +1,116 @@ +/* + * 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 net.consensys.linea.sequencer.txpoolvalidation.validators; + +import static net.consensys.linea.sequencer.modulelimit.ModuleLineCountValidator.ModuleLineCountResult.MODULE_NOT_DEFINED; +import static net.consensys.linea.sequencer.modulelimit.ModuleLineCountValidator.ModuleLineCountResult.TX_MODULE_LINE_COUNT_OVERFLOW; + +import java.util.Map; +import java.util.Optional; + +import lombok.extern.slf4j.Slf4j; +import net.consensys.linea.config.LineaL1L2BridgeConfiguration; +import net.consensys.linea.sequencer.modulelimit.ModuleLimitsValidationResult; +import net.consensys.linea.sequencer.modulelimit.ModuleLineCountValidator; +import net.consensys.linea.zktracer.ZkTracer; +import org.hyperledger.besu.datatypes.Transaction; +import org.hyperledger.besu.plugin.data.BlockHeader; +import org.hyperledger.besu.plugin.services.BlockchainService; +import org.hyperledger.besu.plugin.services.TransactionSimulationService; +import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionPoolValidator; + +@Slf4j +public class SimulationValidator implements PluginTransactionPoolValidator { + private final BlockchainService blockchainService; + private final TransactionSimulationService transactionSimulationService; + private final Map moduleLineLimitsMap; + private final LineaL1L2BridgeConfiguration l1L2BridgeConfiguration; + + public SimulationValidator( + final BlockchainService blockchainService, + final TransactionSimulationService transactionSimulationService, + final Map moduleLineLimitsMap, + final LineaL1L2BridgeConfiguration l1L2BridgeConfiguration) { + this.blockchainService = blockchainService; + this.transactionSimulationService = transactionSimulationService; + this.moduleLineLimitsMap = moduleLineLimitsMap; + this.l1L2BridgeConfiguration = l1L2BridgeConfiguration; + } + + @Override + public Optional validateTransaction( + final Transaction transaction, final boolean isLocal, final boolean hasPriority) { + + final ModuleLineCountValidator moduleLineCountValidator = + new ModuleLineCountValidator(moduleLineLimitsMap); + final var chainHeadHeader = blockchainService.getChainHeadHeader(); + + final var zkTracer = createZkTracer(chainHeadHeader); + final var maybeSimulationResults = + transactionSimulationService.simulate( + transaction, chainHeadHeader.getBlockHash(), zkTracer, true); + + ModuleLimitsValidationResult moduleLimit = + moduleLineCountValidator.validate(zkTracer.getModulesLineCount()); + + if (moduleLimit.getResult() != ModuleLineCountValidator.ModuleLineCountResult.VALID) { + return Optional.of(handleModuleOverLimit(moduleLimit)); + } + + if (maybeSimulationResults.isPresent()) { + final var simulationResult = maybeSimulationResults.get(); + if (simulationResult.isInvalid()) { + return Optional.of( + "Invalid transaction" + + simulationResult.getInvalidReason().map(ir -> ": " + ir).orElse("")); + } + if (!simulationResult.isSuccessful()) { + return Optional.of( + "Reverted transaction" + + simulationResult.getRevertReason().map(rr -> ": " + rr.toHexString()).orElse("")); + } + } + + return Optional.empty(); + } + + private ZkTracer createZkTracer(final BlockHeader chainHeadHeader) { + var zkTracer = new ZkTracer(l1L2BridgeConfiguration); + zkTracer.traceStartConflation(1L); + zkTracer.traceStartBlock(chainHeadHeader); + return zkTracer; + } + + private String handleModuleOverLimit(ModuleLimitsValidationResult moduleLimitResult) { + if (moduleLimitResult.getResult() == MODULE_NOT_DEFINED) { + String moduleNotDefinedMsg = + String.format( + "Module %s does not exist in the limits file.", moduleLimitResult.getModuleName()); + log.error(moduleNotDefinedMsg); + return moduleNotDefinedMsg; + } + if (moduleLimitResult.getResult() == TX_MODULE_LINE_COUNT_OVERFLOW) { + String txOverflowMsg = + String.format( + "Transaction line count for module %s=%s is above the limit %s", + moduleLimitResult.getModuleName(), + moduleLimitResult.getModuleLineCount(), + moduleLimitResult.getModuleLineLimit()); + log.warn(txOverflowMsg); + return txOverflowMsg; + } + return "Internal Error: do not know what to do with result: " + moduleLimitResult.getResult(); + } +} diff --git a/arithmetization/src/test/java/net/consensys/linea/sequencer/txpoolvalidation/validators/SimulationValidatorTest.java b/arithmetization/src/test/java/net/consensys/linea/sequencer/txpoolvalidation/validators/SimulationValidatorTest.java new file mode 100644 index 00000000..5fff8c7c --- /dev/null +++ b/arithmetization/src/test/java/net/consensys/linea/sequencer/txpoolvalidation/validators/SimulationValidatorTest.java @@ -0,0 +1,159 @@ +/* + * 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 net.consensys.linea.sequencer.txpoolvalidation.validators; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.math.BigInteger; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.consensys.linea.config.LineaL1L2BridgeConfiguration; +import net.consensys.linea.config.LineaTracerConfiguration; +import net.consensys.linea.sequencer.modulelimit.ModuleLineCountValidator; +import net.consensys.linea.sequencer.txselection.selectors.TraceLineLimitTransactionSelectorTest; +import org.apache.tuweni.bytes.Bytes; +import org.bouncycastle.asn1.sec.SECNamedCurves; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.hyperledger.besu.crypto.SECPSignature; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.plugin.services.BlockchainService; +import org.hyperledger.besu.plugin.services.TransactionSimulationService; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@Slf4j +@RequiredArgsConstructor +@ExtendWith(MockitoExtension.class) +public class SimulationValidatorTest { + private static final String MODULE_LINE_LIMITS_RESOURCE_NAME = "/sequencer/line-limits.toml"; + public static final Address SENDER = + Address.fromHexString("0x0000000000000000000000000000000000001000"); + public static final Address RECIPIENT = + Address.fromHexString("0x0000000000000000000000000000000000001001"); + private static Wei BASE_FEE = Wei.of(7); + private static Wei PROFITABLE_GAS_PRICE = Wei.of(11000000); + private static final SECPSignature FAKE_SIGNATURE; + private static final Address BRIDGE_CONTRACT = + Address.fromHexString("0x508Ca82Df566dCD1B0DE8296e70a96332cD644ec"); + private static final Bytes BRIDGE_LOG_TOPIC = + Bytes.fromHexString("e856c2b8bd4eb0027ce32eeaf595c21b0b6b4644b326e5b7bd80a1cf8db72e6c"); + + static { + final X9ECParameters params = SECNamedCurves.getByName("secp256k1"); + final ECDomainParameters curve = + new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH()); + FAKE_SIGNATURE = + SECPSignature.create( + new BigInteger( + "66397251408932042429874251838229702988618145381408295790259650671563847073199"), + new BigInteger( + "24729624138373455972486746091821238755870276413282629437244319694880507882088"), + (byte) 0, + curve.getN()); + } + + private Map lineCountLimits; + + @Mock BlockchainService blockchainService; + @Mock TransactionSimulationService transactionSimulationService; + + @TempDir static Path tempDir; + static Path lineLimitsConfPath; + + @BeforeAll + public static void beforeAll() throws IOException { + lineLimitsConfPath = tempDir.resolve("line-limits.toml"); + Files.copy( + TraceLineLimitTransactionSelectorTest.class.getResourceAsStream( + MODULE_LINE_LIMITS_RESOURCE_NAME), + lineLimitsConfPath); + } + + @BeforeEach + public void initialize() { + final var tracerConf = + LineaTracerConfiguration.builder() + .moduleLimitsFilePath(lineLimitsConfPath.toString()) + .build(); + lineCountLimits = new HashMap<>(ModuleLineCountValidator.createLimitModules(tracerConf)); + final var blockHeader = mock(BlockHeader.class); + when(blockHeader.getBaseFee()).thenReturn(Optional.of(BASE_FEE)); + when(blockchainService.getChainHeadHeader()).thenReturn(blockHeader); + } + + private SimulationValidator createSimulationValidator( + final Map lineCountLimits) { + return new SimulationValidator( + blockchainService, + transactionSimulationService, + lineCountLimits, + LineaL1L2BridgeConfiguration.builder() + .contract(BRIDGE_CONTRACT) + .topic(BRIDGE_LOG_TOPIC) + .build()); + } + + @Test + public void successfulTransactionIsValid() { + final var simulationValidator = createSimulationValidator(lineCountLimits); + final org.hyperledger.besu.ethereum.core.Transaction transaction = + org.hyperledger.besu.ethereum.core.Transaction.builder() + .sender(SENDER) + .to(RECIPIENT) + .gasLimit(21000) + .gasPrice(PROFITABLE_GAS_PRICE) + .payload(Bytes.EMPTY) + .value(Wei.ONE) + .signature(FAKE_SIGNATURE) + .build(); + assertThat(simulationValidator.validateTransaction(transaction, false, true)).isEmpty(); + } + + @Test + public void moduleLineCountOverflowTransactionIsInvalid() { + lineCountLimits.put("ADD", 1); + final var simulationValidator = createSimulationValidator(lineCountLimits); + final org.hyperledger.besu.ethereum.core.Transaction transaction = + org.hyperledger.besu.ethereum.core.Transaction.builder() + .sender(SENDER) + .to(RECIPIENT) + .gasLimit(21000) + .gasPrice(PROFITABLE_GAS_PRICE) + .payload(Bytes.repeat((byte) 1, 1000)) + .value(Wei.ONE) + .signature(FAKE_SIGNATURE) + .build(); + assertThat(simulationValidator.validateTransaction(transaction, false, true)) + .contains("Transaction line count for module ADD=2 is above the limit 1"); + } +} diff --git a/arithmetization/src/test/java/net/consensys/linea/sequencer/txselection/selectors/TraceLineLimitTransactionSelectorTest.java b/arithmetization/src/test/java/net/consensys/linea/sequencer/txselection/selectors/TraceLineLimitTransactionSelectorTest.java index ea7f3714..eb9fb983 100644 --- a/arithmetization/src/test/java/net/consensys/linea/sequencer/txselection/selectors/TraceLineLimitTransactionSelectorTest.java +++ b/arithmetization/src/test/java/net/consensys/linea/sequencer/txselection/selectors/TraceLineLimitTransactionSelectorTest.java @@ -22,16 +22,17 @@ import static org.mockito.Mockito.when; import java.io.IOException; -import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; import java.util.Map; -import java.util.stream.Collectors; import net.consensys.linea.config.LineaL1L2BridgeConfiguration; import net.consensys.linea.config.LineaTracerConfiguration; import net.consensys.linea.config.LineaTransactionSelectorConfiguration; +import net.consensys.linea.sequencer.modulelimit.ModuleLineCountValidator; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.toml.Toml; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.PendingTransaction; @@ -40,38 +41,50 @@ import org.hyperledger.besu.plugin.data.TransactionProcessingResult; import org.hyperledger.besu.plugin.data.TransactionSelectionResult; import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelector; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; public class TraceLineLimitTransactionSelectorTest { private static final int OVER_LINE_COUNT_LIMIT_CACHE_SIZE = 2; - private TestableTraceLineLimitTransactionSelector transactionSelector; + private static final String MODULE_LINE_LIMITS_RESOURCE_NAME = "/sequencer/line-limits.toml"; private Map lineCountLimits; + private LineaTracerConfiguration lineaTracerConfiguration; + + @TempDir static Path tempDir; + static Path lineLimitsConfPath; + + @BeforeAll + public static void beforeAll() throws IOException { + lineLimitsConfPath = tempDir.resolve("line-limits.toml"); + Files.copy( + TraceLineLimitTransactionSelectorTest.class.getResourceAsStream( + MODULE_LINE_LIMITS_RESOURCE_NAME), + lineLimitsConfPath); + } @BeforeEach public void initialize() { - lineCountLimits = loadLineCountLimitConf(); - transactionSelector = newSelectorForNewBlock(); - transactionSelector.reset(); + lineaTracerConfiguration = + LineaTracerConfiguration.builder() + .moduleLimitsFilePath(lineLimitsConfPath.toString()) + .build(); + lineCountLimits = + new HashMap<>(ModuleLineCountValidator.createLimitModules(lineaTracerConfiguration)); } - private TestableTraceLineLimitTransactionSelector newSelectorForNewBlock() { + private TestableTraceLineLimitTransactionSelector newSelectorForNewBlock( + final Map lineCountLimits) { return new TestableTraceLineLimitTransactionSelector( - lineCountLimits, "line-limits.toml", OVER_LINE_COUNT_LIMIT_CACHE_SIZE); - } - - private Map loadLineCountLimitConf() { - try (final InputStream is = - this.getClass().getResourceAsStream("/sequencer/line-limits.toml")) { - return Toml.parse(is).getTable("traces-limits").toMap().entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, e -> Math.toIntExact((long) e.getValue()))); - } catch (IOException e) { - throw new RuntimeException(e); - } + lineaTracerConfiguration, lineCountLimits, OVER_LINE_COUNT_LIMIT_CACHE_SIZE); } @Test public void shouldSelectWhenBelowLimits() { + final var transactionSelector = newSelectorForNewBlock(lineCountLimits); + transactionSelector.resetCache(); + final var evaluationContext = mockEvaluationContext(false, 100, Wei.of(1_100_000_000), Wei.of(1_000_000_000), 21000); verifyTransactionSelection( @@ -89,6 +102,9 @@ public void shouldSelectWhenBelowLimits() { @Test public void shouldNotSelectWhenOverLimits() { lineCountLimits.put("ADD", 1); + final var transactionSelector = newSelectorForNewBlock(lineCountLimits); + transactionSelector.resetCache(); + final var evaluationContext = mockEvaluationContext(false, 100, Wei.of(1_100_000_000), Wei.of(1_000_000_000), 21000); verifyTransactionSelection( @@ -106,6 +122,9 @@ public void shouldNotSelectWhenOverLimits() { @Test public void shouldNotReprocessedWhenOverLimits() { lineCountLimits.put("ADD", 1); + var transactionSelector = newSelectorForNewBlock(lineCountLimits); + transactionSelector.resetCache(); + var evaluationContext = mockEvaluationContext(false, 100, Wei.of(1_100_000_000), Wei.of(1_000_000_000), 21000); verifyTransactionSelection( @@ -119,7 +138,7 @@ public void shouldNotReprocessedWhenOverLimits() { transactionSelector.isOverLineCountLimitTxCached( evaluationContext.getPendingTransaction().getTransaction().getHash())) .isTrue(); - transactionSelector = newSelectorForNewBlock(); + transactionSelector = newSelectorForNewBlock(lineCountLimits); assertThat( transactionSelector.isOverLineCountLimitTxCached( evaluationContext.getPendingTransaction().getTransaction().getHash())) @@ -140,6 +159,9 @@ public void shouldNotReprocessedWhenOverLimits() { @Test public void shouldEvictWhenCacheIsFull() { lineCountLimits.put("ADD", 1); + final var transactionSelector = newSelectorForNewBlock(lineCountLimits); + transactionSelector.resetCache(); + final TestTransactionEvaluationContext[] evaluationContexts = new TestTransactionEvaluationContext[OVER_LINE_COUNT_LIMIT_CACHE_SIZE + 1]; for (int i = 0; i <= OVER_LINE_COUNT_LIMIT_CACHE_SIZE; i++) { @@ -222,8 +244,8 @@ private TestTransactionEvaluationContext mockEvaluationContext( private class TestableTraceLineLimitTransactionSelector extends TraceLineLimitTransactionSelector { TestableTraceLineLimitTransactionSelector( + final LineaTracerConfiguration lineaTracerConfiguration, final Map moduleLimits, - final String limitFilePath, final int overLimitCacheSize) { super( moduleLimits, @@ -234,10 +256,10 @@ private class TestableTraceLineLimitTransactionSelector .contract(Address.fromHexString("0xDEADBEEF")) .topic(Bytes.fromHexString("0x012345")) .build(), - LineaTracerConfiguration.builder().moduleLimitsFilePath(limitFilePath).build()); + lineaTracerConfiguration); } - void reset() { + void resetCache() { overLineCountLimitCache.clear(); }