Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add profitable transaction selector #530

Merged
merged 10 commits into from
Jan 10, 2024
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
Loading