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/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())); + } +}