Skip to content

Commit

Permalink
Add profitable transaction selector (#530)
Browse files Browse the repository at this point in the history
* Add profitable transaction selector

Signed-off-by: Fabio Di Fabio <[email protected]>

* Set default min margin to 1.0

Co-authored-by: Julien Marchand <[email protected]>

* Pass TransactionSelectionContext to all methods of plugin selector

Signed-off-by: Fabio Di Fabio <[email protected]>

* log each tx margin at trace level

* Update arithmetization/src/main/java/net/consensys/linea/sequencer/txselection/selectors/ProfitableTransactionSelector.java

Co-authored-by: Roman Vaseev <[email protected]>

* Add a test for verifying that a prev unprofitable tx is selected after a gas price bump
Signed-off-by: Fabio Di Fabio <[email protected]>

* Enforce validation on tx selection parameters that must be positive

Signed-off-by: Fabio Di Fabio <[email protected]>

* Acceptance test for profitable tx selector

Signed-off-by: Fabio Di Fabio <[email protected]>

---------

Signed-off-by: Fabio Di Fabio <[email protected]>
Co-authored-by: Julien Marchand <[email protected]>
Co-authored-by: Roman Vaseev <[email protected]>
  • Loading branch information
3 people authored Jan 10, 2024
1 parent d6f5e2d commit 4c81fa0
Show file tree
Hide file tree
Showing 17 changed files with 626 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
import org.apache.commons.lang3.RandomStringUtils;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration;
import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase;
import org.hyperledger.besu.tests.acceptance.dsl.account.Accounts;
import org.hyperledger.besu.tests.acceptance.dsl.condition.txpool.TxPoolConditions;
Expand Down Expand Up @@ -65,6 +67,11 @@ public void setup() throws Exception {
minerNode =
besu.createCliqueNodeWithExtraCliOptions(
"miner1", LINEA_CLIQUE_OPTIONS, getTestCliOptions());
minerNode.setTransactionPoolConfiguration(
ImmutableTransactionPoolConfiguration.builder()
.from(TransactionPoolConfiguration.DEFAULT)
.noLocalPriority(true)
.build());
cluster.start(minerNode);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* 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;

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

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.account.TransferTransaction;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.web3j.crypto.Credentials;
import org.web3j.protocol.Web3j;
import org.web3j.tx.RawTransactionManager;
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 Wei MIN_GAS_PRICE = Wei.of(1_000_000_000);

@Override
public List<String> 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();
}

@BeforeEach
public void setMinGasPrice() {
minerNode.getMiningParameters().setMinTransactionGasPrice(MIN_GAS_PRICE);
}

@Test
public void transactionIsNotMinedWhenUnprofitable() 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);

final String txData = "not profitable transaction".repeat(10);

final var txUnprofitable =
txManager.sendTransaction(
MIN_GAS_PRICE.getAsBigInteger(),
BigInteger.valueOf(MAX_TX_GAS_LIMIT / 2),
credentials.getAddress(),
txData,
BigInteger.ZERO);

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()));

// assert that tx below margin is not confirmed
minerNode.verify(eth.expectNoTransactionReceipt(txUnprofitable.getTransactionHash()));
}

/**
* if we have a list of transactions [t_small, t_tooBig, t_small, ..., t_small] where t_tooBig is
* too big to fit in a block, we have blocks created that contain all t_small transactions.
*
* @throws Exception if send transaction fails
*/
@Test
public void transactionIsMinedWhenProfitable() {
minerNode.getMiningParameters().setMinTransactionGasPrice(MIN_GAS_PRICE);
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()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public TestCommandLineOptionsBuilder set(String option, String value) {
}

public List<String> build() {
List<String> optionsList = new ArrayList<>();
List<String> optionsList = new ArrayList<>(cliOptions.size());
for (String key : cliOptions.stringPropertyNames()) {
optionsList.add(key + cliOptions.getProperty(key));
}
Expand Down
1 change: 1 addition & 0 deletions arithmetization/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ dependencies {
implementation 'io.tmio:tuweni-units'

implementation 'org.bouncycastle:bcprov-jdk18on'
implementation 'org.hibernate.validator:hibernate-validator'

testImplementation "${besuArtifactGroup}:evm"
testImplementation "${besuArtifactGroup}:besu-datatypes"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ private enum LineaStatus implements TransactionSelectionResult.Status {
BLOCK_MODULE_LINE_COUNT_FULL(true, false),
TX_GAS_EXCEEDS_USER_MAX_BLOCK_GAS(false, true),
TX_TOO_LARGE_FOR_REMAINING_USER_GAS(false, false),
TX_MODULE_LINE_COUNT_OVERFLOW(false, true);
TX_MODULE_LINE_COUNT_OVERFLOW(false, true),
TX_UNPROFITABLE(false, false);

private final boolean stop;
private final boolean discard;
Expand Down Expand Up @@ -57,4 +58,7 @@ protected LineaTransactionSelectionResult(LineaStatus status) {
new LineaTransactionSelectionResult(LineaStatus.TX_TOO_LARGE_FOR_REMAINING_USER_GAS);
public static final TransactionSelectionResult TX_MODULE_LINE_COUNT_OVERFLOW =
new LineaTransactionSelectionResult(LineaStatus.TX_MODULE_LINE_COUNT_OVERFLOW);

public static final TransactionSelectionResult TX_UNPROFITABLE =
new LineaTransactionSelectionResult(LineaStatus.TX_UNPROFITABLE);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,45 +15,85 @@

package net.consensys.linea.sequencer.txselection;

import java.math.BigDecimal;

import com.google.common.base.MoreObjects;
import jakarta.validation.constraints.Positive;
import picocli.CommandLine;

/** The Linea CLI options. */
public class LineaTransactionSelectorCliOptions {
public static final int DEFAULT_MAX_BLOCK_CALLDATA_SIZE = 70000;
public static final int DEFAULT_MAX_BLOCK_CALLDATA_SIZE = 70_000;
private static final String DEFAULT_MODULE_LIMIT_FILE_PATH = "moduleLimitFile.toml";
public static final long DEFAULT_MAX_GAS_PER_BLOCK = 30_000_000L;
public static final int DEFAULT_VERIFICATION_GAS_COST = 1_200_000;
public static final int DEFAULT_VERIFICATION_CAPACITY = 90_000;
public static final int DEFAULT_GAS_PRICE_RATIO = 15;
public static final BigDecimal DEFAULT_MIN_MARGIN = BigDecimal.ONE;
private static final String MAX_BLOCK_CALLDATA_SIZE = "--plugin-linea-max-block-calldata-size";
private static final String MODULE_LIMIT_FILE_PATH = "--plugin-linea-module-limit-file-path";
private static final String MAX_GAS_PER_BLOCK = "--plugin-linea-max-block-gas";
private static final String VERIFICATION_GAS_COST = "--plugin-linea-verification-gas-cost";
private static final String VERIFICATION_CAPACITY = "--plugin-linea-verification-capacity";
private static final String GAS_PRICE_RATIO = "--plugin-linea-gas-price-ratio";
private static final String MIN_MARGIN = "--plugin-linea-min-margin";

@Positive
@CommandLine.Option(
names = {MAX_BLOCK_CALLDATA_SIZE},
hidden = true,
paramLabel = "<INTEGER>",
description =
"Maximum size for the calldata of a Block (default: "
+ DEFAULT_MAX_BLOCK_CALLDATA_SIZE
+ ")")
description = "Maximum size for the calldata of a block (default: ${DEFAULT-VALUE})")
private int maxBlockCallDataSize = DEFAULT_MAX_BLOCK_CALLDATA_SIZE;

@CommandLine.Option(
names = {MODULE_LIMIT_FILE_PATH},
hidden = true,
paramLabel = "<STRING>",
description =
"Path to the toml file containing the module limits (default: "
+ DEFAULT_MODULE_LIMIT_FILE_PATH
+ ")")
"Path to the toml file containing the module limits (default: ${DEFAULT-VALUE})")
private String moduleLimitFilePath = DEFAULT_MODULE_LIMIT_FILE_PATH;

@Positive
@CommandLine.Option(
names = {MAX_GAS_PER_BLOCK},
hidden = true,
paramLabel = "<LONG>",
description = "Sets max gas per block.")
description = "Sets max gas per block (default: ${DEFAULT-VALUE})")
private Long maxGasPerBlock = DEFAULT_MAX_GAS_PER_BLOCK;

@Positive
@CommandLine.Option(
names = {VERIFICATION_GAS_COST},
hidden = true,
paramLabel = "<INTEGER>",
description = "L1 verification gas cost (default: ${DEFAULT-VALUE})")
private int verificationGasCost = DEFAULT_VERIFICATION_GAS_COST;

@Positive
@CommandLine.Option(
names = {VERIFICATION_CAPACITY},
hidden = true,
paramLabel = "<INTEGER>",
description = "L1 verification capacity (default: ${DEFAULT-VALUE})")
private int verificationCapacity = DEFAULT_VERIFICATION_CAPACITY;

@Positive
@CommandLine.Option(
names = {GAS_PRICE_RATIO},
hidden = true,
paramLabel = "<INTEGER>",
description = "L1/L2 gas price ratio (default: ${DEFAULT-VALUE})")
private int gasPriceRatio = DEFAULT_GAS_PRICE_RATIO;

@Positive
@CommandLine.Option(
names = {MIN_MARGIN},
hidden = true,
paramLabel = "<FLOAT>",
description = "Minimum margin of a transaction to be selected (default: ${DEFAULT-VALUE})")
private BigDecimal minMargin = DEFAULT_MIN_MARGIN;

private LineaTransactionSelectorCliOptions() {}

/**
Expand All @@ -77,6 +117,10 @@ public static LineaTransactionSelectorCliOptions fromConfig(
options.maxBlockCallDataSize = config.maxBlockCallDataSize();
options.moduleLimitFilePath = config.moduleLimitsFilePath();
options.maxGasPerBlock = config.maxGasPerBlock();
options.verificationGasCost = config.getVerificationGasCost();
options.verificationCapacity = config.getVerificationCapacity();
options.gasPriceRatio = config.getGasPriceRatio();
options.minMargin = BigDecimal.valueOf(config.getMinMargin());
return options;
}

Expand All @@ -90,6 +134,10 @@ public LineaTransactionSelectorConfiguration toDomainObject() {
.maxBlockCallDataSize(maxBlockCallDataSize)
.moduleLimits(moduleLimitFilePath)
.maxGasPerBlock(maxGasPerBlock)
.verificationGasCost(verificationGasCost)
.verificationCapacity(verificationCapacity)
.gasPriceRatio(gasPriceRatio)
.minMargin(minMargin.doubleValue())
.build();
}

Expand All @@ -99,6 +147,10 @@ public String toString() {
.add(MAX_BLOCK_CALLDATA_SIZE, maxBlockCallDataSize)
.add(MODULE_LIMIT_FILE_PATH, moduleLimitFilePath)
.add(MAX_GAS_PER_BLOCK, maxGasPerBlock)
.add(VERIFICATION_GAS_COST, verificationGasCost)
.add(VERIFICATION_CAPACITY, verificationCapacity)
.add(GAS_PRICE_RATIO, gasPriceRatio)
.add(MIN_MARGIN, minMargin)
.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,26 @@ public final class LineaTransactionSelectorConfiguration {
private final int maxBlockCallDataSize;
private final String moduleLimitsFilePath;
private final long maxGasPerBlock;
private final int verificationGasCost;
private final int verificationCapacity;
private final int gasPriceRatio;
private final double minMargin;

private LineaTransactionSelectorConfiguration(
int maxBlockCallDataSize, final String moduleLimitsFilePath, long maxGasPerBlock) {
final int maxBlockCallDataSize,
final String moduleLimitsFilePath,
final long maxGasPerBlock,
final int verificationGasCost,
final int verificationCapacity,
final int gasPriceRatio,
final double minMargin) {
this.maxBlockCallDataSize = maxBlockCallDataSize;
this.moduleLimitsFilePath = moduleLimitsFilePath;
this.maxGasPerBlock = maxGasPerBlock;
this.verificationGasCost = verificationGasCost;
this.verificationCapacity = verificationCapacity;
this.gasPriceRatio = gasPriceRatio;
this.minMargin = minMargin;
}

public int maxBlockCallDataSize() {
Expand All @@ -40,10 +54,30 @@ public long maxGasPerBlock() {
return maxGasPerBlock;
}

public int getVerificationGasCost() {
return verificationGasCost;
}

public int getVerificationCapacity() {
return verificationCapacity;
}

public int getGasPriceRatio() {
return gasPriceRatio;
}

public double getMinMargin() {
return minMargin;
}

public static class Builder {
private int maxBlockCallDataSize;
private String moduleLimitsFilePath;
private long maxGasPerBlock;
private int verificationGasCost;
private int verificationCapacity;
private int gasPriceRatio;
private double minMargin;

public Builder maxBlockCallDataSize(final int maxBlockCallDataSize) {
this.maxBlockCallDataSize = maxBlockCallDataSize;
Expand All @@ -55,14 +89,40 @@ public Builder moduleLimits(final String moduleLimitFilePath) {
return this;
}

public Builder maxGasPerBlock(long maxGasPerBlock) {
public Builder maxGasPerBlock(final long maxGasPerBlock) {
this.maxGasPerBlock = maxGasPerBlock;
return this;
}

public Builder verificationGasCost(final int verificationGasCost) {
this.verificationGasCost = verificationGasCost;
return this;
}

public Builder verificationCapacity(final int verificationCapacity) {
this.verificationCapacity = verificationCapacity;
return this;
}

public Builder gasPriceRatio(final int gasPriceRatio) {
this.gasPriceRatio = gasPriceRatio;
return this;
}

public Builder minMargin(final double minMargin) {
this.minMargin = minMargin;
return this;
}

public LineaTransactionSelectorConfiguration build() {
return new LineaTransactionSelectorConfiguration(
maxBlockCallDataSize, moduleLimitsFilePath, maxGasPerBlock);
maxBlockCallDataSize,
moduleLimitsFilePath,
maxGasPerBlock,
verificationGasCost,
verificationCapacity,
gasPriceRatio,
minMargin);
}
}
}
Loading

0 comments on commit 4c81fa0

Please sign in to comment.