diff --git a/acceptance-tests/src/test/java/linea/plugin/acc/test/LineaPluginTestBase.java b/acceptance-tests/src/test/java/linea/plugin/acc/test/LineaPluginTestBase.java index 38a898f1..625fb32a 100644 --- a/acceptance-tests/src/test/java/linea/plugin/acc/test/LineaPluginTestBase.java +++ b/acceptance-tests/src/test/java/linea/plugin/acc/test/LineaPluginTestBase.java @@ -67,7 +67,7 @@ public class LineaPluginTestBase extends AcceptanceTestBase { public void setup() throws Exception { minerNode = besu.createCliqueNodeWithExtraCliOptionsAndRpcApis( - "miner1", LINEA_CLIQUE_OPTIONS, getTestCliOptions(), Set.of("LINEA")); + "miner1", LINEA_CLIQUE_OPTIONS, getTestCliOptions(), Set.of("LINEA", "MINER")); minerNode.setTransactionPoolConfiguration( ImmutableTransactionPoolConfiguration.builder() .from(TransactionPoolConfiguration.DEFAULT) diff --git a/acceptance-tests/src/test/java/linea/plugin/acc/test/ProfitableTransactionTest.java b/acceptance-tests/src/test/java/linea/plugin/acc/test/ProfitableTransactionTest.java index 5ac2a6cc..ca2cf265 100644 --- a/acceptance-tests/src/test/java/linea/plugin/acc/test/ProfitableTransactionTest.java +++ b/acceptance-tests/src/test/java/linea/plugin/acc/test/ProfitableTransactionTest.java @@ -30,18 +30,12 @@ import org.web3j.tx.TransactionManager; public class ProfitableTransactionTest extends LineaPluginTestBase { - private static final int VERIFICATION_GAS_COST = 1_200_000; - private static final int VERIFICATION_CAPACITY = 90_000; - private static final int GAS_PRICE_RATIO = 15; - private static final double MIN_MARGIN = 1.0; + private static final double MIN_MARGIN = 1.5; private static final Wei MIN_GAS_PRICE = Wei.of(1_000_000_000); @Override public List getTestCliOptions() { return new TestCommandLineOptionsBuilder() - .set("--plugin-linea-verification-gas-cost=", String.valueOf(VERIFICATION_GAS_COST)) - .set("--plugin-linea-verification-capacity=", String.valueOf(VERIFICATION_CAPACITY)) - .set("--plugin-linea-gas-price-ratio=", String.valueOf(GAS_PRICE_RATIO)) .set("--plugin-linea-min-margin=", String.valueOf(MIN_MARGIN)) .build(); } @@ -70,7 +64,7 @@ public void transactionIsNotMinedWhenUnprofitable() throws Exception { final var txUnprofitable = txManager.sendTransaction( - MIN_GAS_PRICE.getAsBigInteger(), + MIN_GAS_PRICE.getAsBigInteger().divide(BigInteger.valueOf(100)), BigInteger.valueOf(MAX_TX_GAS_LIMIT / 2), credentials.getAddress(), txData.toString(), diff --git a/acceptance-tests/src/test/java/linea/plugin/acc/test/extradata/ExtraDataPricingTest.java b/acceptance-tests/src/test/java/linea/plugin/acc/test/extradata/ExtraDataPricingTest.java new file mode 100644 index 00000000..d489b865 --- /dev/null +++ b/acceptance-tests/src/test/java/linea/plugin/acc/test/extradata/ExtraDataPricingTest.java @@ -0,0 +1,162 @@ +/* + * 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.extradata; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.List; + +import linea.plugin.acc.test.LineaPluginTestBase; +import linea.plugin.acc.test.TestCommandLineOptionsBuilder; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt32; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.tests.acceptance.dsl.account.Account; +import org.hyperledger.besu.tests.acceptance.dsl.account.Accounts; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.account.TransferTransaction; +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.Request; +import org.web3j.protocol.core.methods.response.EthSendTransaction; +import org.web3j.utils.Numeric; + +public class ExtraDataPricingTest extends LineaPluginTestBase { + private static final Wei MIN_GAS_PRICE = Wei.of(1_000_000_000); + private static final int WEI_IN_KWEI = 1000; + + @Override + public List getTestCliOptions() { + return getTestCommandLineOptionsBuilder().build(); + } + + protected TestCommandLineOptionsBuilder getTestCommandLineOptionsBuilder() { + return new TestCommandLineOptionsBuilder() + .set("--plugin-linea-extra-data-pricing-enabled=", Boolean.TRUE.toString()); + } + + @Test + public void updateMinGasPriceViaExtraData() { + minerNode.getMiningParameters().setMinTransactionGasPrice(MIN_GAS_PRICE); + final var doubleMinGasPrice = MIN_GAS_PRICE.multiply(2); + + final var extraData = + createExtraDataPricingField( + 0, MIN_GAS_PRICE.toLong() / WEI_IN_KWEI, doubleMinGasPrice.toLong() / WEI_IN_KWEI); + final var reqSetExtraData = new ExtraDataPricingTest.MinerSetExtraDataRequest(extraData); + final var respSetExtraData = reqSetExtraData.execute(minerNode.nodeRequests()); + + assertThat(respSetExtraData).isTrue(); + + final Account sender = accounts.getSecondaryBenefactor(); + final Account recipient = accounts.createAccount("recipient"); + + final TransferTransaction transferTx = accountTransactions.createTransfer(sender, recipient, 1); + final var txHash = minerNode.execute(transferTx); + + minerNode.verify(eth.expectSuccessfulTransactionReceipt(txHash.toHexString())); + + assertThat(minerNode.getMiningParameters().getMinTransactionGasPrice()) + .isEqualTo(doubleMinGasPrice); + } + + @Test + public void updateProfitabilityParamsViaExtraData() throws IOException { + final Web3j web3j = minerNode.nodeRequests().eth(); + final Account sender = accounts.getSecondaryBenefactor(); + final Account recipient = accounts.createAccount("recipient"); + minerNode.getMiningParameters().setMinTransactionGasPrice(MIN_GAS_PRICE); + + final var extraData = + createExtraDataPricingField( + MIN_GAS_PRICE.multiply(2).toLong() / WEI_IN_KWEI, + MIN_GAS_PRICE.toLong() / WEI_IN_KWEI, + MIN_GAS_PRICE.toLong() / WEI_IN_KWEI); + final var reqSetExtraData = new ExtraDataPricingTest.MinerSetExtraDataRequest(extraData); + final var respSetExtraData = reqSetExtraData.execute(minerNode.nodeRequests()); + + assertThat(respSetExtraData).isTrue(); + + // when this first tx is mined the above extra data pricing will have effect on following txs + final TransferTransaction profitableTx = + accountTransactions.createTransfer(sender, recipient, 1); + final var protitableTx = minerNode.execute(profitableTx); + + minerNode.verify(eth.expectSuccessfulTransactionReceipt(protitableTx.toHexString())); + + // this tx will be evaluated with the previously set extra data pricing to be unprofitable + final RawTransaction unprofitableTx = + RawTransaction.createTransaction( + BigInteger.ZERO, + MIN_GAS_PRICE.getAsBigInteger(), + BigInteger.valueOf(21000), + recipient.getAddress(), + ""); + + final byte[] signedUnprofitableTx = + TransactionEncoder.signMessage( + unprofitableTx, Credentials.create(Accounts.GENESIS_ACCOUNT_ONE_PRIVATE_KEY)); + + final EthSendTransaction signedUnprofitableTxResp = + web3j.ethSendRawTransaction(Numeric.toHexString(signedUnprofitableTx)).send(); + + assertThat(signedUnprofitableTxResp.hasError()).isTrue(); + assertThat(signedUnprofitableTxResp.getError().getMessage()).isEqualTo("Gas price too low"); + + assertThat(getTxPoolContent()).isEmpty(); + } + + static class MinerSetExtraDataRequest implements Transaction { + private final Bytes32 extraData; + + public MinerSetExtraDataRequest(final Bytes32 extraData) { + this.extraData = extraData; + } + + @Override + public Boolean execute(final NodeRequests nodeRequests) { + try { + return new Request<>( + "miner_setExtraData", + List.of(extraData.toHexString()), + nodeRequests.getWeb3jService(), + MinerSetExtraDataResponse.class) + .send() + .getResult(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + static class MinerSetExtraDataResponse extends org.web3j.protocol.core.Response {} + } + + private Bytes32 createExtraDataPricingField( + final long fixedCostKWei, final long variableCostKWei, final long minGasPriceKWei) { + final UInt32 fixed = UInt32.valueOf(BigInteger.valueOf(fixedCostKWei)); + final UInt32 variable = UInt32.valueOf(BigInteger.valueOf(variableCostKWei)); + final UInt32 min = UInt32.valueOf(BigInteger.valueOf(minGasPriceKWei)); + + return Bytes32.rightPad( + Bytes.concatenate(Bytes.of((byte) 1), fixed.toBytes(), variable.toBytes(), min.toBytes())); + } +} diff --git a/acceptance-tests/src/test/java/linea/plugin/acc/test/rpc/linea/EstimateGasCompatibilityModeTest.java b/acceptance-tests/src/test/java/linea/plugin/acc/test/rpc/linea/EstimateGasCompatibilityModeTest.java index 6c0e8aa1..623c2f1f 100644 --- a/acceptance-tests/src/test/java/linea/plugin/acc/test/rpc/linea/EstimateGasCompatibilityModeTest.java +++ b/acceptance-tests/src/test/java/linea/plugin/acc/test/rpc/linea/EstimateGasCompatibilityModeTest.java @@ -40,7 +40,6 @@ public List getTestCliOptions() { protected void assertIsProfitable( final Transaction tx, final Wei baseFee, - final Wei estimatedPriorityFee, final Wei estimatedMaxGasPrice, final long estimatedGasLimit) { final var minGasPrice = minerNode.getMiningParameters().getMinTransactionGasPrice(); @@ -62,6 +61,6 @@ protected void assertIsProfitable( protected void assertMinGasPriceLowerBound(final Wei baseFee, final Wei estimatedMaxGasPrice) { // since we are in compatibility mode, we want to check that returned profitable priority fee is // the min priority fee per gas * multiplier + base fee - assertIsProfitable(null, baseFee, null, estimatedMaxGasPrice, 0); + assertIsProfitable(null, baseFee, estimatedMaxGasPrice, 0); } } diff --git a/acceptance-tests/src/test/java/linea/plugin/acc/test/rpc/linea/EstimateGasTest.java b/acceptance-tests/src/test/java/linea/plugin/acc/test/rpc/linea/EstimateGasTest.java index df80e487..a06891f9 100644 --- a/acceptance-tests/src/test/java/linea/plugin/acc/test/rpc/linea/EstimateGasTest.java +++ b/acceptance-tests/src/test/java/linea/plugin/acc/test/rpc/linea/EstimateGasTest.java @@ -45,13 +45,11 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.web3j.protocol.core.Request; -import org.web3j.protocol.core.Response; import org.web3j.protocol.http.HttpService; public class EstimateGasTest extends LineaPluginTestBase { - protected static final int VERIFICATION_GAS_COST = 1_200_000; - protected static final int VERIFICATION_CAPACITY = 90_000; - protected static final int GAS_PRICE_RATIO = 15; + protected static final int FIXED_GAS_COST_WEI = 0; + protected static final int VARIABLE_GAS_COST_WEI = 1_000_000_000; protected static final double MIN_MARGIN = 1.0; protected static final double ESTIMATE_GAS_MIN_MARGIN = 1.0; protected static final Wei MIN_GAS_PRICE = Wei.of(1_000_000_000); @@ -65,9 +63,8 @@ public List getTestCliOptions() { protected TestCommandLineOptionsBuilder getTestCommandLineOptionsBuilder() { return new TestCommandLineOptionsBuilder() - .set("--plugin-linea-verification-gas-cost=", String.valueOf(VERIFICATION_GAS_COST)) - .set("--plugin-linea-verification-capacity=", String.valueOf(VERIFICATION_CAPACITY)) - .set("--plugin-linea-gas-price-ratio=", String.valueOf(GAS_PRICE_RATIO)) + .set("--plugin-linea-fixed-gas-cost-wei=", String.valueOf(FIXED_GAS_COST_WEI)) + .set("--plugin-linea-variable-gas-cost-wei=", String.valueOf(VARIABLE_GAS_COST_WEI)) .set("--plugin-linea-min-margin=", String.valueOf(MIN_MARGIN)) .set("--plugin-linea-estimate-gas-min-margin=", String.valueOf(ESTIMATE_GAS_MIN_MARGIN)) .set("--plugin-linea-max-tx-gas-limit=", String.valueOf(MAX_TRANSACTION_GAS_LIMIT)); @@ -82,9 +79,8 @@ public void setMinGasPrice() { public void createDefaultConfigurations() { profitabilityConf = LineaProfitabilityCliOptions.create().toDomainObject().toBuilder() - .verificationCapacity(VERIFICATION_CAPACITY) - .verificationGasCost(VERIFICATION_GAS_COST) - .gasPriceRatio(GAS_PRICE_RATIO) + .fixedCostWei(FIXED_GAS_COST_WEI) + .variableCostWei(VARIABLE_GAS_COST_WEI) .minMargin(MIN_MARGIN) .estimateGasMinMargin(ESTIMATE_GAS_MIN_MARGIN) .build(); @@ -145,13 +141,12 @@ public void lineaEstimateGasIsProfitable() { .signature(LineaEstimateGas.FAKE_SIGNATURE_FOR_SIZE_CALCULATION) .build(); - assertIsProfitable(tx, baseFee, estimatedPriorityFee, estimatedMaxGasPrice, estimatedGasLimit); + assertIsProfitable(tx, baseFee, estimatedMaxGasPrice, estimatedGasLimit); } protected void assertIsProfitable( final org.hyperledger.besu.ethereum.core.Transaction tx, final Wei baseFee, - final Wei estimatedPriorityFee, final Wei estimatedMaxGasPrice, final long estimatedGasLimit) { @@ -159,20 +154,16 @@ protected void assertIsProfitable( final var profitabilityCalculator = new TransactionProfitabilityCalculator(profitabilityConf); - final var profitablePriorityFee = - profitabilityCalculator.profitablePriorityFeePerGas( - tx, profitabilityConf.txPoolMinMargin(), minGasPrice, estimatedGasLimit); - - assertThat(profitablePriorityFee.greaterThan(minGasPrice)).isTrue(); + assertThat(estimatedMaxGasPrice.greaterOrEqualThan(minGasPrice)).isTrue(); assertThat( profitabilityCalculator.isProfitable( "Test", tx, - profitabilityConf.txPoolMinMargin(), - minerNode.getMiningParameters().getMinTransactionGasPrice(), + profitabilityConf.estimateGasMinMargin(), estimatedMaxGasPrice, - estimatedGasLimit)) + estimatedGasLimit, + minGasPrice)) .isTrue(); } diff --git a/arithmetization/src/main/java/net/consensys/linea/extradata/LineaExtraDataHandler.java b/arithmetization/src/main/java/net/consensys/linea/extradata/LineaExtraDataHandler.java new file mode 100644 index 00000000..2aeed3c6 --- /dev/null +++ b/arithmetization/src/main/java/net/consensys/linea/extradata/LineaExtraDataHandler.java @@ -0,0 +1,145 @@ +/* + * 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.extradata; + +import java.util.function.Consumer; +import java.util.function.Function; + +import lombok.extern.slf4j.Slf4j; +import net.consensys.linea.config.LineaProfitabilityConfiguration; +import org.apache.commons.lang3.mutable.MutableLong; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.UInt32; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.datatypes.rpc.JsonRpcResponseType; +import org.hyperledger.besu.plugin.data.AddedBlockContext; +import org.hyperledger.besu.plugin.data.BlockHeader; +import org.hyperledger.besu.plugin.services.BesuEvents; +import org.hyperledger.besu.plugin.services.BlockchainService; +import org.hyperledger.besu.plugin.services.RpcEndpointService; + +@Slf4j +public class LineaExtraDataHandler implements BesuEvents.BlockAddedListener { + private final RpcEndpointService rpcEndpointService; + private final ExtraDataParser[] extraDataParsers; + + public LineaExtraDataHandler( + final RpcEndpointService rpcEndpointService, + final BlockchainService blockchainService, + final LineaProfitabilityConfiguration profitabilityConf) { + this.rpcEndpointService = rpcEndpointService; + extraDataParsers = new ExtraDataParser[] {new Version1Parser(profitabilityConf)}; + onStartup(blockchainService); + } + + private void onStartup(final BlockchainService blockchainService) { + consumeExtraData(blockchainService.getChainHeadHeader()); + } + + @Override + public void onBlockAdded(final AddedBlockContext addedBlockContext) { + consumeExtraData(addedBlockContext.getBlockHeader()); + } + + private void consumeExtraData(final BlockHeader blockHeader) { + final var rawExtraData = blockHeader.getExtraData(); + + if (!Bytes.EMPTY.equals(rawExtraData)) { + for (final ExtraDataParser extraDataParser : extraDataParsers) { + if (extraDataParser.canParse(rawExtraData)) { + final var extraData = rawExtraData.slice(1); + extraDataParser.parse(extraData); + return; + } + } + log.warn("unsupported extra data field {}", rawExtraData.toHexString()); + } + } + + private interface ExtraDataParser { + boolean canParse(Bytes extraData); + + void parse(Bytes extraData); + + static Long toLong(final Bytes fieldBytes) { + return UInt32.fromBytes(fieldBytes).toLong(); + } + } + + @SuppressWarnings("rawtypes") + private class Version1Parser implements ExtraDataParser { + private static final int WEI_IN_KWEI = 1_000; + private final LineaProfitabilityConfiguration profitabilityConf; + private final FieldConsumer[] fieldsSequence; + private final MutableLong currFixedCostKWei = new MutableLong(); + private final MutableLong currVariableCostKWei = new MutableLong(); + + public Version1Parser(final LineaProfitabilityConfiguration profitabilityConf) { + this.profitabilityConf = profitabilityConf; + + final FieldConsumer fixedGasCostField = + new FieldConsumer<>( + "fixedGasCost", 4, ExtraDataParser::toLong, currFixedCostKWei::setValue); + final FieldConsumer variableGasCostField = + new FieldConsumer<>( + "variableGasCost", 4, ExtraDataParser::toLong, currVariableCostKWei::setValue); + final FieldConsumer minGasPriceField = + new FieldConsumer<>("minGasPrice", 4, ExtraDataParser::toLong, this::updateMinGasPrice); + + this.fieldsSequence = + new FieldConsumer[] {fixedGasCostField, variableGasCostField, minGasPriceField}; + } + + public boolean canParse(final Bytes rawExtraData) { + return rawExtraData.get(0) == (byte) 1; + } + + public synchronized void parse(final Bytes extraData) { + log.info("Parsing extra data version 1: {}", extraData.toHexString()); + int startIndex = 0; + for (final FieldConsumer fieldConsumer : fieldsSequence) { + fieldConsumer.accept(extraData.slice(startIndex, fieldConsumer.length)); + startIndex += fieldConsumer.length; + } + + profitabilityConf.updateFixedAndVariableCost( + currFixedCostKWei.longValue() * WEI_IN_KWEI, + currVariableCostKWei.longValue() * WEI_IN_KWEI); + } + + void updateMinGasPrice(final Long minGasPriceKWei) { + final var minGasPriceWei = Wei.of(minGasPriceKWei).multiply(WEI_IN_KWEI); + final var resp = + rpcEndpointService.call( + "miner_setMinGasPrice", new Object[] {minGasPriceWei.toShortHexString()}); + if (!resp.getType().equals(JsonRpcResponseType.SUCCESS)) { + log.error("setMinGasPrice failed: {}", resp); + } + } + } + + private record FieldConsumer( + String name, int length, Function converter, Consumer consumer) + implements Consumer { + + @Override + public void accept(final Bytes fieldBytes) { + final var converted = converter.apply(fieldBytes); + log.debug("Field {}={} (raw bytes: {})", name, converted, fieldBytes.toHexString()); + consumer.accept(converted); + } + } +} diff --git a/arithmetization/src/main/java/net/consensys/linea/extradata/LineaExtraDataPlugin.java b/arithmetization/src/main/java/net/consensys/linea/extradata/LineaExtraDataPlugin.java new file mode 100644 index 00000000..d3243f55 --- /dev/null +++ b/arithmetization/src/main/java/net/consensys/linea/extradata/LineaExtraDataPlugin.java @@ -0,0 +1,75 @@ +/* + * 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.extradata; + +import java.util.Optional; + +import com.google.auto.service.AutoService; +import lombok.extern.slf4j.Slf4j; +import net.consensys.linea.AbstractLineaRequiredPlugin; +import org.hyperledger.besu.plugin.BesuContext; +import org.hyperledger.besu.plugin.BesuPlugin; +import org.hyperledger.besu.plugin.services.BesuEvents; +import org.hyperledger.besu.plugin.services.BlockchainService; +import org.hyperledger.besu.plugin.services.RpcEndpointService; + +/** This plugin registers handlers that are activated when new blocks are imported */ +@Slf4j +@AutoService(BesuPlugin.class) +public class LineaExtraDataPlugin extends AbstractLineaRequiredPlugin { + public static final String NAME = "linea"; + private BesuEvents besuEventsService; + private RpcEndpointService rpcEndpointService; + private BlockchainService blockchainService; + + @Override + public Optional getName() { + return Optional.of(NAME); + } + + @Override + public void doRegister(final BesuContext context) { + besuEventsService = + context + .getService(BesuEvents.class) + .orElseThrow( + () -> new RuntimeException("Failed to obtain BesuEvents from the BesuContext.")); + rpcEndpointService = + context + .getService(RpcEndpointService.class) + .orElseThrow( + () -> + new RuntimeException( + "Failed to obtain RpcEndpointService from the BesuContext.")); + blockchainService = + context + .getService(BlockchainService.class) + .orElseThrow( + () -> + new RuntimeException( + "Failed to obtain BlockchainService from the BesuContext.")); + } + + @Override + public void start() { + super.start(); + if (profitabilityConfiguration.extraDataPricingEnabled()) { + besuEventsService.addBlockAddedListener( + new LineaExtraDataHandler( + rpcEndpointService, blockchainService, profitabilityConfiguration)); + } + } +} diff --git a/gradle.properties b/gradle.properties index 3f1c2a1d..48878dea 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ releaseVersion=0.1.4-SNAPSHOT -besuVersion=24.5-develop-62f4326 +besuVersion=24.5-develop-0cfa46b besuArtifactGroup=io.consensys.linea-besu distributionIdentifier=besu-sequencer-plugins distributionBaseUrl=https://artifacts.consensys.net/public/linea-besu/raw/names/linea-besu.tar.gz/versions/ diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index c36c00d2..3ab55011 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -41,6 +41,7 @@ repositories { content { includeGroupByRegex('com\\.splunk\\..*') } } mavenCentral() + mavenLocal() } configurations.all { diff --git a/gradle/dist.gradle b/gradle/dist.gradle index 7882d93c..68e54518 100644 --- a/gradle/dist.gradle +++ b/gradle/dist.gradle @@ -27,16 +27,37 @@ tasks.register('javadocJar', Jar) { from javadoc.destinationDir } +def lineaBesuDistTar = new File(buildDir, rootProject.besuFilename) + +tasks.register('copyLocalLineaBesu', Copy) { + onlyIf { + project.hasProperty('useLocalLineaBesuDir') + } + def localLineaBesuDir = "${findProperty('useLocalLineaBesuDir')}".replaceFirst('^~', System.getProperty('user.home')) + doFirst { + if (!file(localLineaBesuDir).exists()) { + throw new GradleException("${localLineaBesuDir} not found") + } + } + + from new File("${localLineaBesuDir}/build/distributions/${rootProject.besuFilename}") + into lineaBesuDistTar.parentFile +} + tasks.register('downloadLatestLineaBesu', Download) { - src rootProject.besuUrl - dest new File(buildDir, rootProject.besuFilename) - onlyIfModified true + onlyIf { + !project.hasProperty('useLocalLineaBesuDir') + } + src rootProject.besuUrl + dest lineaBesuDistTar + onlyIfModified true } version = project.hasProperty('releaseVersion') ? project.getProperty('releaseVersion') : 'snapshot' jar { dependsOn downloadLatestLineaBesu + dependsOn copyLocalLineaBesu archiveBaseName = distributionIdentifier @@ -76,8 +97,9 @@ static def getCheckedOutGitCommitHash() { tasks.register('distTar', Tar) { dependsOn jar dependsOn downloadLatestLineaBesu + dependsOn copyLocalLineaBesu - from(tarTree(downloadLatestLineaBesu.dest), { + from(tarTree(lineaBesuDistTar), { eachFile { path = path.replaceFirst(rootProject.besuIdentifier, '') } includeEmptyDirs = false exclude "**/LICENSE" diff --git a/sequencer/src/main/java/net/consensys/linea/bl/TransactionProfitabilityCalculator.java b/sequencer/src/main/java/net/consensys/linea/bl/TransactionProfitabilityCalculator.java index d31d093b..fa01afff 100644 --- a/sequencer/src/main/java/net/consensys/linea/bl/TransactionProfitabilityCalculator.java +++ b/sequencer/src/main/java/net/consensys/linea/bl/TransactionProfitabilityCalculator.java @@ -25,68 +25,56 @@ @Slf4j public class TransactionProfitabilityCalculator { - private final LineaProfitabilityConfiguration profitabilityConf; - private final double preComputedValue; - private final double priceAdjustment; public TransactionProfitabilityCalculator( final LineaProfitabilityConfiguration profitabilityConf) { this.profitabilityConf = profitabilityConf; - this.preComputedValue = - profitabilityConf.gasPriceRatio() * profitabilityConf.verificationGasCost(); - this.priceAdjustment = profitabilityConf.gasPriceAdjustment().getAsBigInteger().doubleValue(); } public Wei profitablePriorityFeePerGas( final Transaction transaction, final double minMargin, - final Wei minGasPrice, - final long gas) { - final double compressedTxSize = getCompressedTxSize(transaction); + final long gas, + final Wei minGasPriceWei) { + final int compressedTxSize = getCompressedTxSize(transaction); + + final long variableCostWei = + profitabilityConf.extraDataPricingEnabled() + ? profitabilityConf.variableCostWei() + : minGasPriceWei.toLong(); final var profitAt = - (preComputedValue - * compressedTxSize - * minGasPrice.getAsBigInteger().doubleValue() - / (gas * profitabilityConf.verificationCapacity()) - + priceAdjustment) - * minMargin; + minMargin * (variableCostWei * compressedTxSize / gas + profitabilityConf.fixedCostWei()); - final var adjustedProfit = Wei.ofNumber(BigDecimal.valueOf(profitAt).toBigInteger()); + final var profitAtWei = Wei.ofNumber(BigDecimal.valueOf(profitAt).toBigInteger()); log.atDebug() .setMessage( - "Estimated profitable priorityFeePerGas: {}; estimateGasMinMargin={}, verificationCapacity={}, " - + "verificationGasCost={}, gasPriceRatio={}, gasPriceAdjustment={}, gas={}, minGasPrice={}, " - + "l1GasPrice={}, txSize={}, compressedTxSize={}") - .addArgument(adjustedProfit::toHumanReadableString) - .addArgument(profitabilityConf.estimateGasMinMargin()) - .addArgument(profitabilityConf.verificationCapacity()) - .addArgument(profitabilityConf.verificationGasCost()) - .addArgument(profitabilityConf.gasPriceRatio()) - .addArgument(profitabilityConf.gasPriceAdjustment()::toHumanReadableString) + "Estimated profitable priorityFeePerGas: {}; minMargin={}, fixedCostWei={}, " + + "variableCostWei={}, gas={}, txSize={}, compressedTxSize={}") + .addArgument(profitAtWei::toHumanReadableString) + .addArgument(minMargin) + .addArgument(profitabilityConf.fixedCostWei()) + .addArgument(variableCostWei) .addArgument(gas) - .addArgument(minGasPrice::toHumanReadableString) - .addArgument( - () -> minGasPrice.multiply(profitabilityConf.gasPriceRatio()).toHumanReadableString()) .addArgument(transaction::getSize) .addArgument(compressedTxSize) .log(); - return adjustedProfit; + return profitAtWei; } public boolean isProfitable( final String context, final Transaction transaction, final double minMargin, - final Wei minGasPrice, final Wei effectiveGasPrice, - final long gas) { + final long gas, + final Wei minGasPriceWei) { final Wei profitablePriorityFee = - profitablePriorityFeePerGas(transaction, minMargin, minGasPrice, gas); + profitablePriorityFeePerGas(transaction, minMargin, gas, minGasPriceWei); if (effectiveGasPrice.lessThan(profitablePriorityFee)) { log( @@ -97,7 +85,7 @@ public boolean isProfitable( effectiveGasPrice, profitablePriorityFee, gas, - minGasPrice); + minGasPriceWei); return false; } @@ -109,11 +97,11 @@ public boolean isProfitable( effectiveGasPrice, profitablePriorityFee, gas, - minGasPrice); + minGasPriceWei); return true; } - private double getCompressedTxSize(final Transaction transaction) { + private int getCompressedTxSize(final Transaction transaction) { final byte[] bytes = transaction.encoded().toArrayUnsafe(); return LibCompress.CompressedSize(bytes, bytes.length); } @@ -126,11 +114,12 @@ private void log( final Wei effectiveGasPrice, final Wei profitableGasPrice, final long gasUsed, - final Wei minGasPrice) { + final Wei minGasPriceWei) { + leb.setMessage( "Context {}. Transaction {} has a margin of {}, minMargin={}, effectiveGasPrice={}," - + " profitableGasPrice={}, verificationCapacity={}, verificationGasCost={}, gasPriceRatio={},, gasPriceAdjustment={}" - + " gasUsed={}, minGasPrice={}") + + " profitableGasPrice={}, fixedCostWei={}, variableCostWei={}, " + + " gasUsed={}") .addArgument(context) .addArgument(transaction::getHash) .addArgument( @@ -140,12 +129,13 @@ private void log( .addArgument(minMargin) .addArgument(effectiveGasPrice::toHumanReadableString) .addArgument(profitableGasPrice::toHumanReadableString) - .addArgument(profitabilityConf.verificationCapacity()) - .addArgument(profitabilityConf.verificationGasCost()) - .addArgument(profitabilityConf.gasPriceRatio()) - .addArgument(profitabilityConf.gasPriceAdjustment()::toHumanReadableString) + .addArgument(profitabilityConf.fixedCostWei()) + .addArgument( + () -> + profitabilityConf.extraDataPricingEnabled() + ? profitabilityConf.variableCostWei() + : minGasPriceWei.toLong()) .addArgument(gasUsed) - .addArgument(minGasPrice::toHumanReadableString) .log(); } } diff --git a/sequencer/src/main/java/net/consensys/linea/config/LineaProfitabilityCliOptions.java b/sequencer/src/main/java/net/consensys/linea/config/LineaProfitabilityCliOptions.java index 14683bb5..ea48ae7d 100644 --- a/sequencer/src/main/java/net/consensys/linea/config/LineaProfitabilityCliOptions.java +++ b/sequencer/src/main/java/net/consensys/linea/config/LineaProfitabilityCliOptions.java @@ -19,23 +19,15 @@ import com.google.common.base.MoreObjects; import jakarta.validation.constraints.Positive; -import net.consensys.linea.config.converters.WeiConverter; -import org.hyperledger.besu.datatypes.Wei; import picocli.CommandLine; /** The Linea profitability calculator CLI options. */ public class LineaProfitabilityCliOptions { - public static final String VERIFICATION_GAS_COST = "--plugin-linea-verification-gas-cost"; - public static final int DEFAULT_VERIFICATION_GAS_COST = 1_200_000; + public static final String FIXED_GAS_COST_WEI = "--plugin-linea-fixed-gas-cost-wei"; + public static final long DEFAULT_FIXED_GAS_COST_WEI = 0; - public static final String VERIFICATION_CAPACITY = "--plugin-linea-verification-capacity"; - public static final int DEFAULT_VERIFICATION_CAPACITY = 90_000; - - public static final String GAS_PRICE_RATIO = "--plugin-linea-gas-price-ratio"; - public static final int DEFAULT_GAS_PRICE_RATIO = 15; - - public static final String GAS_PRICE_ADJUSTMENT = "--plugin-linea-gas-price-adjustment"; - public static final Wei DEFAULT_GAS_PRICE_ADJUSTMENT = Wei.ZERO; + public static final String VARIABLE_GAS_COST_WEI = "--plugin-linea-variable-gas-cost-wei"; + public static final long DEFAULT_VARIABLE_GAS_COST_WEI = 1_000_000_000; public static final String MIN_MARGIN = "--plugin-linea-min-margin"; public static final BigDecimal DEFAULT_MIN_MARGIN = BigDecimal.ONE; @@ -54,38 +46,25 @@ public class LineaProfitabilityCliOptions { "--plugin-linea-tx-pool-profitability-check-p2p-enabled"; public static final boolean DEFAULT_TX_POOL_ENABLE_CHECK_P2P = false; - @Positive - @CommandLine.Option( - names = {VERIFICATION_GAS_COST}, - hidden = true, - paramLabel = "", - description = "L1 verification gas cost (default: ${DEFAULT-VALUE})") - private int verificationGasCost = DEFAULT_VERIFICATION_GAS_COST; + public static final String EXTRA_DATA_PRICING_ENABLED = + "--plugin-linea-extra-data-pricing-enabled"; + public static final boolean DEFAULT_EXTRA_DATA_PRICING_ENABLED = false; @Positive @CommandLine.Option( - names = {VERIFICATION_CAPACITY}, + names = {FIXED_GAS_COST_WEI}, hidden = true, paramLabel = "", - description = "L1 verification capacity (default: ${DEFAULT-VALUE})") - private int verificationCapacity = DEFAULT_VERIFICATION_CAPACITY; + description = "Fixed gas cost in Wei (default: ${DEFAULT-VALUE})") + private long fixedGasCostWei = DEFAULT_FIXED_GAS_COST_WEI; @Positive @CommandLine.Option( - names = {GAS_PRICE_RATIO}, + names = {VARIABLE_GAS_COST_WEI}, hidden = true, paramLabel = "", - description = "L1/L2 gas price ratio (default: ${DEFAULT-VALUE})") - private int gasPriceRatio = DEFAULT_GAS_PRICE_RATIO; - - @CommandLine.Option( - names = {GAS_PRICE_ADJUSTMENT}, - hidden = true, - converter = WeiConverter.class, - paramLabel = "", - description = - "Amount to add to the calculated profitable gas price (default: ${DEFAULT-VALUE})") - private Wei gasPriceAdjustment = DEFAULT_GAS_PRICE_ADJUSTMENT; + description = "Variable gas cost in Wei (default: ${DEFAULT-VALUE})") + private long variableGasCostWei = DEFAULT_VARIABLE_GAS_COST_WEI; @Positive @CommandLine.Option( @@ -131,6 +110,15 @@ public class LineaProfitabilityCliOptions { "Enable the profitability check for txs received via p2p? (default: ${DEFAULT-VALUE})") private boolean txPoolCheckP2pEnabled = DEFAULT_TX_POOL_ENABLE_CHECK_P2P; + @CommandLine.Option( + names = {EXTRA_DATA_PRICING_ENABLED}, + arity = "0..1", + hidden = true, + paramLabel = "", + description = + "Enable setting pricing parameters via extra data field (default: ${DEFAULT-VALUE})") + private boolean extraDataPricingEnabled = DEFAULT_EXTRA_DATA_PRICING_ENABLED; + private LineaProfitabilityCliOptions() {} /** @@ -151,15 +139,14 @@ public static LineaProfitabilityCliOptions create() { public static LineaProfitabilityCliOptions fromConfig( final LineaProfitabilityConfiguration config) { final LineaProfitabilityCliOptions options = create(); - options.verificationGasCost = config.verificationGasCost(); - options.verificationCapacity = config.verificationCapacity(); - options.gasPriceRatio = config.gasPriceRatio(); - options.gasPriceAdjustment = config.gasPriceAdjustment(); + options.fixedGasCostWei = config.fixedCostWei(); + options.variableGasCostWei = config.variableCostWei(); options.minMargin = BigDecimal.valueOf(config.minMargin()); options.estimageGasMinMargin = BigDecimal.valueOf(config.estimateGasMinMargin()); options.txPoolMinMargin = BigDecimal.valueOf(config.txPoolMinMargin()); options.txPoolCheckApiEnabled = config.txPoolCheckApiEnabled(); options.txPoolCheckP2pEnabled = config.txPoolCheckP2pEnabled(); + options.extraDataPricingEnabled = config.extraDataPricingEnabled(); return options; } @@ -170,30 +157,28 @@ public static LineaProfitabilityCliOptions fromConfig( */ public LineaProfitabilityConfiguration toDomainObject() { return LineaProfitabilityConfiguration.builder() - .verificationGasCost(verificationGasCost) - .verificationCapacity(verificationCapacity) - .gasPriceRatio(gasPriceRatio) - .gasPriceAdjustment(gasPriceAdjustment) + .fixedCostWei(fixedGasCostWei) + .variableCostWei(variableGasCostWei) .minMargin(minMargin.doubleValue()) .estimateGasMinMargin(estimageGasMinMargin.doubleValue()) .txPoolMinMargin(txPoolMinMargin.doubleValue()) .txPoolCheckApiEnabled(txPoolCheckApiEnabled) .txPoolCheckP2pEnabled(txPoolCheckP2pEnabled) + .extraDataPricingEnabled(extraDataPricingEnabled) .build(); } @Override public String toString() { return MoreObjects.toStringHelper(this) - .add(VERIFICATION_GAS_COST, verificationGasCost) - .add(VERIFICATION_CAPACITY, verificationCapacity) - .add(GAS_PRICE_RATIO, gasPriceRatio) - .add(GAS_PRICE_ADJUSTMENT, gasPriceAdjustment) + .add(FIXED_GAS_COST_WEI, fixedGasCostWei) + .add(VARIABLE_GAS_COST_WEI, variableGasCostWei) .add(MIN_MARGIN, minMargin) .add(ESTIMATE_GAS_MIN_MARGIN, estimageGasMinMargin) .add(TX_POOL_MIN_MARGIN, txPoolMinMargin) .add(TX_POOL_ENABLE_CHECK_API, txPoolCheckApiEnabled) .add(TX_POOL_ENABLE_CHECK_P2P, txPoolCheckP2pEnabled) + .add(EXTRA_DATA_PRICING_ENABLED, extraDataPricingEnabled) .toString(); } } diff --git a/sequencer/src/main/java/net/consensys/linea/config/LineaProfitabilityConfiguration.java b/sequencer/src/main/java/net/consensys/linea/config/LineaProfitabilityConfiguration.java index 514371eb..dfc4d517 100644 --- a/sequencer/src/main/java/net/consensys/linea/config/LineaProfitabilityConfiguration.java +++ b/sequencer/src/main/java/net/consensys/linea/config/LineaProfitabilityConfiguration.java @@ -16,17 +16,45 @@ package net.consensys.linea.config; import lombok.Builder; -import org.hyperledger.besu.datatypes.Wei; +import lombok.Getter; +import lombok.ToString; +import lombok.experimental.Accessors; /** The Linea profitability calculator configuration. */ @Builder(toBuilder = true) -public record LineaProfitabilityConfiguration( - int verificationGasCost, - int verificationCapacity, - int gasPriceRatio, - Wei gasPriceAdjustment, - double minMargin, - double estimateGasMinMargin, - double txPoolMinMargin, - boolean txPoolCheckApiEnabled, - boolean txPoolCheckP2pEnabled) {} +@Accessors(fluent = true) +@Getter +@ToString +public class LineaProfitabilityConfiguration { + /** It is safe to keep this as long, since it will store value <= max_int * 1000 */ + private long fixedCostWei; + /** It is safe to keep this as long, since it will store value <= max_int * 1000 */ + private long variableCostWei; + + private double minMargin; + private double estimateGasMinMargin; + private double txPoolMinMargin; + private boolean txPoolCheckApiEnabled; + private boolean txPoolCheckP2pEnabled; + private boolean extraDataPricingEnabled; + + /** + * These 2 parameters must be atomically updated + * + * @param fixedCostWei fixed cost in Wei + * @param variableCostWei variable cost in Wei + */ + public synchronized void updateFixedAndVariableCost( + final long fixedCostWei, final long variableCostWei) { + this.fixedCostWei = fixedCostWei; + this.variableCostWei = variableCostWei; + } + + public synchronized long fixedCostWei() { + return fixedCostWei; + } + + public synchronized long variableCostWei() { + return variableCostWei; + } +} diff --git a/sequencer/src/main/java/net/consensys/linea/config/LineaRpcConfiguration.java b/sequencer/src/main/java/net/consensys/linea/config/LineaRpcConfiguration.java index 6551c924..4945013b 100644 --- a/sequencer/src/main/java/net/consensys/linea/config/LineaRpcConfiguration.java +++ b/sequencer/src/main/java/net/consensys/linea/config/LineaRpcConfiguration.java @@ -18,8 +18,17 @@ import java.math.BigDecimal; import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; /** The Linea RPC configuration. */ @Builder(toBuilder = true) -public record LineaRpcConfiguration( - boolean estimateGasCompatibilityModeEnabled, BigDecimal estimateGasCompatibilityMultiplier) {} +@Accessors(fluent = true) +@Getter +@ToString +public class LineaRpcConfiguration { + @Setter private volatile boolean estimateGasCompatibilityModeEnabled; + private BigDecimal estimateGasCompatibilityMultiplier; +} diff --git a/sequencer/src/main/java/net/consensys/linea/rpc/linea/LineaEstimateGas.java b/sequencer/src/main/java/net/consensys/linea/rpc/linea/LineaEstimateGas.java index d97957a7..a390ff7c 100644 --- a/sequencer/src/main/java/net/consensys/linea/rpc/linea/LineaEstimateGas.java +++ b/sequencer/src/main/java/net/consensys/linea/rpc/linea/LineaEstimateGas.java @@ -43,6 +43,7 @@ import org.bouncycastle.crypto.params.ECDomainParameters; import org.hyperledger.besu.crypto.SECPSignature; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.datatypes.rpc.RpcMethodError; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonCallParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter; @@ -55,7 +56,6 @@ import org.hyperledger.besu.plugin.services.TransactionSimulationService; import org.hyperledger.besu.plugin.services.exception.PluginRpcEndpointException; import org.hyperledger.besu.plugin.services.rpc.PluginRpcRequest; -import org.hyperledger.besu.plugin.services.rpc.RpcMethodError; @Slf4j public class LineaEstimateGas { @@ -188,7 +188,7 @@ private Wei getEstimatedPriorityFee( final Wei profitablePriorityFee = txProfitabilityCalculator.profitablePriorityFeePerGas( - transaction, profitabilityConf.estimateGasMinMargin(), minGasPrice, estimatedGasUsed); + transaction, profitabilityConf.estimateGasMinMargin(), estimatedGasUsed, minGasPrice); if (profitablePriorityFee.greaterOrEqualThan(priorityFeeLowerBound)) { return profitablePriorityFee; diff --git a/sequencer/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/validators/ProfitabilityValidator.java b/sequencer/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/validators/ProfitabilityValidator.java index 97d25fd5..12be4142 100644 --- a/sequencer/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/validators/ProfitabilityValidator.java +++ b/sequencer/src/main/java/net/consensys/linea/sequencer/txpoolvalidation/validators/ProfitabilityValidator.java @@ -55,9 +55,9 @@ public Optional validateTransaction( "Txpool", transaction, profitabilityConf.txPoolMinMargin(), - besuConfiguration.getMinGasPrice(), calculateUpfrontGasPrice(transaction), - transaction.getGasLimit()) + transaction.getGasLimit(), + besuConfiguration.getMinGasPrice()) ? Optional.empty() : Optional.of("Gas price too low"); } diff --git a/sequencer/src/main/java/net/consensys/linea/sequencer/txselection/selectors/ProfitableTransactionSelector.java b/sequencer/src/main/java/net/consensys/linea/sequencer/txselection/selectors/ProfitableTransactionSelector.java index 65e89d74..5c5c53bd 100644 --- a/sequencer/src/main/java/net/consensys/linea/sequencer/txselection/selectors/ProfitableTransactionSelector.java +++ b/sequencer/src/main/java/net/consensys/linea/sequencer/txselection/selectors/ProfitableTransactionSelector.java @@ -80,9 +80,9 @@ public TransactionSelectionResult evaluateTransactionPreProcessing( "PreProcessing", transaction, profitabilityConf.minMargin(), - minGasPrice, evaluationContext.getTransactionGasPrice(), - gasLimit)) { + gasLimit, + minGasPrice)) { return TX_UNPROFITABLE_UPFRONT; } @@ -133,9 +133,9 @@ public TransactionSelectionResult evaluateTransactionPostProcessing( "PostProcessing", transaction, profitabilityConf.minMargin(), - evaluationContext.getMinGasPrice(), evaluationContext.getTransactionGasPrice(), - gasUsed)) { + gasUsed, + evaluationContext.getMinGasPrice())) { rememberUnprofitable(transaction); return TX_UNPROFITABLE; } diff --git a/sequencer/src/test/java/net/consensys/linea/sequencer/txpoolvalidation/validators/ProfitabilityValidatorTest.java b/sequencer/src/test/java/net/consensys/linea/sequencer/txpoolvalidation/validators/ProfitabilityValidatorTest.java index 5a8a1143..3178a5ec 100644 --- a/sequencer/src/test/java/net/consensys/linea/sequencer/txpoolvalidation/validators/ProfitabilityValidatorTest.java +++ b/sequencer/src/test/java/net/consensys/linea/sequencer/txpoolvalidation/validators/ProfitabilityValidatorTest.java @@ -47,8 +47,8 @@ public class ProfitabilityValidatorTest { Address.fromHexString("0x0000000000000000000000000000000000001000"); public static final Address RECIPIENT = Address.fromHexString("0x0000000000000000000000000000000000001001"); - private static Wei PROFITABLE_GAS_PRICE = Wei.of(11000000); - private static Wei UNPROFITABLE_GAS_PRICE = Wei.of(1000000); + private static Wei PROFITABLE_GAS_PRICE = Wei.of(11_000_000); + private static Wei UNPROFITABLE_GAS_PRICE = Wei.of(200_000); private static final SECPSignature FAKE_SIGNATURE; static { diff --git a/sequencer/src/test/java/net/consensys/linea/sequencer/txselection/selectors/ProfitableTransactionSelectorTest.java b/sequencer/src/test/java/net/consensys/linea/sequencer/txselection/selectors/ProfitableTransactionSelectorTest.java index a4ce76ae..a4635a65 100644 --- a/sequencer/src/test/java/net/consensys/linea/sequencer/txselection/selectors/ProfitableTransactionSelectorTest.java +++ b/sequencer/src/test/java/net/consensys/linea/sequencer/txselection/selectors/ProfitableTransactionSelectorTest.java @@ -41,10 +41,9 @@ import org.junit.jupiter.api.Test; public class ProfitableTransactionSelectorTest { - private static final int VERIFICATION_GAS_COST = 1_200_000; - private static final int VERIFICATION_CAPACITY = 90_000; - private static final int GAS_PRICE_RATIO = 15; - private static final double MIN_MARGIN = 1.0; + private static final int FIXED_GAS_COST_WEI = 600_000; + private static final int VARIABLE_GAS_COST_WEI = 1_000_000; + private static final double MIN_MARGIN = 1.5; private static final int UNPROFITABLE_CACHE_SIZE = 2; private static final int UNPROFITABLE_RETRY_LIMIT = 1; private final LineaTransactionSelectorConfiguration txSelectorConf = @@ -54,10 +53,9 @@ public class ProfitableTransactionSelectorTest { .build(); private final LineaProfitabilityConfiguration profitabilityConf = LineaProfitabilityCliOptions.create().toDomainObject().toBuilder() - .gasPriceRatio(GAS_PRICE_RATIO) .minMargin(MIN_MARGIN) - .verificationCapacity(VERIFICATION_CAPACITY) - .verificationGasCost(VERIFICATION_GAS_COST) + .fixedCostWei(FIXED_GAS_COST_WEI) + .variableCostWei(VARIABLE_GAS_COST_WEI) .build(); private TestableProfitableTransactionSelector transactionSelector; @@ -82,23 +80,12 @@ public void shouldSelectWhenProfitable() { SELECTED); } - @Test - public void shouldSelectWhenProfitableWithAdjustedSize() { - var mockTransactionProcessingResult = mockTransactionProcessingResult(21000); - verifyTransactionSelection( - transactionSelector, - mockEvaluationContext(false, 150, Wei.of(1_100_000_000), Wei.of(1_000_000_000), 21000), - mockTransactionProcessingResult, - SELECTED, - SELECTED); - } - @Test public void shouldNotSelectWhenUnprofitableUpfront() { var mockTransactionProcessingResult = mockTransactionProcessingResult(21000); verifyTransactionSelection( transactionSelector, - mockEvaluationContext(false, 1000, Wei.of(1_100_000_000), Wei.of(1_000_000_000), 21000), + mockEvaluationContext(false, 10000, Wei.of(1_000_100), Wei.of(1_000_000), 21000), mockTransactionProcessingResult, TX_UNPROFITABLE_UPFRONT, null); @@ -109,7 +96,7 @@ public void shouldNotSelectWhenUnprofitable() { var mockTransactionProcessingResult = mockTransactionProcessingResult(21000); verifyTransactionSelection( transactionSelector, - mockEvaluationContext(false, 1000, Wei.of(1_100_000_000), Wei.of(1_000_000_000), 210000), + mockEvaluationContext(false, 10000, Wei.of(1_000_100), Wei.of(1_000_000), 210000), mockTransactionProcessingResult, SELECTED, TX_UNPROFITABLE); @@ -142,7 +129,7 @@ public void shouldSelectPriorityTxEvenWhenUnprofitable() { public void shouldRetryUnprofitableTxWhenBelowLimit() { var mockTransactionProcessingResult = mockTransactionProcessingResult(21000); var mockEvaluationContext = - mockEvaluationContext(false, 1000, Wei.of(1_100_000_000), Wei.of(1_000_000_000), 210000); + mockEvaluationContext(false, 10000, Wei.of(1_000_010), Wei.of(1_000_000), 210000); // first try verifyTransactionSelection( transactionSelector, @@ -177,7 +164,7 @@ public void shouldEvictWhenUnprofitableCacheIsFull() { for (int i = 0; i <= UNPROFITABLE_CACHE_SIZE; i++) { var mockTransactionProcessingResult = mockTransactionProcessingResult(21000); var mockEvaluationContext = - mockEvaluationContext(false, 1000, Wei.of(1_100_000_000), Wei.of(1_000_000_000), 210000); + mockEvaluationContext(false, 10000, Wei.of(1_000_010), Wei.of(1_000_000), 210000); evaluationContexts[i] = mockEvaluationContext; verifyTransactionSelection( transactionSelector, @@ -207,10 +194,10 @@ public void shouldEvictWhenUnprofitableCacheIsFull() { @Test public void shouldNotRetryUnprofitableTxWhenRetryLimitReached() { - var minGasPriceBlock1 = Wei.of(1_000_000_000); + var minGasPriceBlock1 = Wei.of(1_000_000); var mockTransactionProcessingResult1 = mockTransactionProcessingResult(21000); var mockEvaluationContext1 = - mockEvaluationContext(false, 1000, Wei.of(1_100_000_000), minGasPriceBlock1, 210000); + mockEvaluationContext(false, 10000, Wei.of(1_000_010), minGasPriceBlock1, 210000); // first try of first tx verifyTransactionSelection( transactionSelector, @@ -221,7 +208,7 @@ public void shouldNotRetryUnprofitableTxWhenRetryLimitReached() { var mockTransactionProcessingResult2 = mockTransactionProcessingResult(21000); var mockEvaluationContext2 = - mockEvaluationContext(false, 1000, Wei.of(1_100_000_000), minGasPriceBlock1, 210000); + mockEvaluationContext(false, 10000, Wei.of(1_000_010), minGasPriceBlock1, 210000); // first try of second tx verifyTransactionSelection( transactionSelector, @@ -242,7 +229,7 @@ public void shouldNotRetryUnprofitableTxWhenRetryLimitReached() { // simulate another block transactionSelector = newSelectorForNewBlock(); // we need to decrease the min gas price in order to allow a retry - var minGasPriceBlock2 = Wei.of(1_000_000_000).subtract(1); + var minGasPriceBlock2 = minGasPriceBlock1.subtract(1); // we should remember of the unprofitable txs for the new block assertThat( @@ -273,10 +260,10 @@ public void shouldNotRetryUnprofitableTxWhenRetryLimitReached() { @Test public void shouldNotRetryUnprofitableTxWhenMinGasPriceNotDecreased() { - var minGasPriceBlock1 = Wei.of(1_000_000_000); + var minGasPriceBlock1 = Wei.of(1_000_000); var mockTransactionProcessingResult1 = mockTransactionProcessingResult(21000); var mockEvaluationContext1 = - mockEvaluationContext(false, 1000, Wei.of(1_100_000_000), minGasPriceBlock1, 210000); + mockEvaluationContext(false, 10000, Wei.of(1_000_010), minGasPriceBlock1, 210000); // first try of first tx verifyTransactionSelection( transactionSelector, @@ -312,10 +299,10 @@ public void shouldNotRetryUnprofitableTxWhenMinGasPriceNotDecreased() { @Test public void profitableAndUnprofitableTxsMix() { - var minGasPriceBlock1 = Wei.of(1_000_000_000); + var minGasPriceBlock1 = Wei.of(1_000_000); var mockTransactionProcessingResult1 = mockTransactionProcessingResult(21000); var mockEvaluationContext1 = - mockEvaluationContext(false, 1000, Wei.of(1_100_000_000), minGasPriceBlock1, 210000); + mockEvaluationContext(false, 10000, Wei.of(1_000_010), minGasPriceBlock1, 210000); // first try of first tx verifyTransactionSelection( transactionSelector, @@ -326,7 +313,7 @@ public void profitableAndUnprofitableTxsMix() { var mockTransactionProcessingResult2 = mockTransactionProcessingResult(21000); var mockEvaluationContext2 = - mockEvaluationContext(false, 100, Wei.of(1_100_000_000), minGasPriceBlock1, 210000); + mockEvaluationContext(false, 1000, Wei.of(1_000_010), minGasPriceBlock1, 210000); // first try of second tx verifyTransactionSelection( transactionSelector,