diff --git a/rskj-core/src/main/java/co/rsk/RskContext.java b/rskj-core/src/main/java/co/rsk/RskContext.java index 37fe50231a5..b16fd8dc057 100644 --- a/rskj-core/src/main/java/co/rsk/RskContext.java +++ b/rskj-core/src/main/java/co/rsk/RskContext.java @@ -544,7 +544,8 @@ public synchronized Ethereum getRsk() { getTransactionGateway(), getCompositeEthereumListener(), getBlockchain(), - getGasPriceTracker() + getGasPriceTracker(), + getRskSystemProperties().getMinGasPriceMultiplier() ); } diff --git a/rskj-core/src/main/java/org/ethereum/config/SystemProperties.java b/rskj-core/src/main/java/org/ethereum/config/SystemProperties.java index 69f42e73797..f35d2b00fc0 100644 --- a/rskj-core/src/main/java/org/ethereum/config/SystemProperties.java +++ b/rskj-core/src/main/java/org/ethereum/config/SystemProperties.java @@ -92,6 +92,7 @@ public abstract class SystemProperties { private static final String PROPERTY_RPC_GAS_ESTIMATION_CAP = "rpc.gasEstimationCap"; private static final String PROPERTY_RPC_CALL_GAS_CAP = "rpc.callGasCap"; private static final String PROPERTY_RPC_MAX_RESPONSE_SIZE = "rpc.maxResponseSize"; + private static final String PROPERTY_RPC_MIN_GAS_PRICE_MULTIPLIER = "rpc.minGasPriceMultiplier"; private static final String PROPERTY_RPC_TIMEOUT = "rpc.timeout"; public static final String PROPERTY_PUBLIC_IP = "public.ip"; @@ -738,6 +739,14 @@ public long getCallGasCap() { return configFromFiles.getLong(PROPERTY_RPC_CALL_GAS_CAP); } + public double getMinGasPriceMultiplier() { + if (!configFromFiles.hasPath(PROPERTY_RPC_MIN_GAS_PRICE_MULTIPLIER)) { + return 1.1; + } + + return configFromFiles.getDouble(PROPERTY_RPC_MIN_GAS_PRICE_MULTIPLIER); + } + public long getRpcTimeout() { if (!configFromFiles.hasPath(PROPERTY_RPC_TIMEOUT)) { return 0L; diff --git a/rskj-core/src/main/java/org/ethereum/facade/EthereumImpl.java b/rskj-core/src/main/java/org/ethereum/facade/EthereumImpl.java index 4226956e39d..8b3cec0a317 100644 --- a/rskj-core/src/main/java/org/ethereum/facade/EthereumImpl.java +++ b/rskj-core/src/main/java/org/ethereum/facade/EthereumImpl.java @@ -28,6 +28,7 @@ import org.ethereum.net.server.ChannelManager; import javax.annotation.Nonnull; +import java.math.BigDecimal; public class EthereumImpl implements Ethereum { @@ -36,13 +37,15 @@ public class EthereumImpl implements Ethereum { private final CompositeEthereumListener compositeEthereumListener; private final Blockchain blockchain; private final GasPriceTracker gasPriceTracker; + private final double minGasPriceMultiplier; public EthereumImpl( ChannelManager channelManager, TransactionGateway transactionGateway, CompositeEthereumListener compositeEthereumListener, Blockchain blockchain, - GasPriceTracker gasPriceTracker) { + GasPriceTracker gasPriceTracker, + double minGasPriceMultiplier) { this.channelManager = channelManager; this.transactionGateway = transactionGateway; @@ -51,6 +54,7 @@ public EthereumImpl( this.gasPriceTracker = gasPriceTracker; compositeEthereumListener.addListener(gasPriceTracker); + this.minGasPriceMultiplier = minGasPriceMultiplier; } @Override @@ -80,6 +84,10 @@ public TransactionPoolAddResult submitTransaction(Transaction transaction) { @Override public Coin getGasPrice() { - return gasPriceTracker.getGasPrice(); + if (gasPriceTracker.isFeeMarketWorking()) { + return gasPriceTracker.getGasPrice(); + } + double estimatedGasPrice = blockchain.getBestBlock().getMinimumGasPrice().asBigInteger().doubleValue() * minGasPriceMultiplier; + return new Coin(BigDecimal.valueOf(estimatedGasPrice).toBigInteger()); } } diff --git a/rskj-core/src/main/java/org/ethereum/listener/GasPriceTracker.java b/rskj-core/src/main/java/org/ethereum/listener/GasPriceTracker.java index 36b98783615..964a6f30015 100644 --- a/rskj-core/src/main/java/org/ethereum/listener/GasPriceTracker.java +++ b/rskj-core/src/main/java/org/ethereum/listener/GasPriceTracker.java @@ -68,16 +68,16 @@ public class GasPriceTracker extends EthereumListenerAdapter { private Coin lastVal; + private GasPriceTracker(BlockStore blockStore) { + this.blockStore = blockStore; + } + public static GasPriceTracker create(BlockStore blockStore) { GasPriceTracker gasPriceTracker = new GasPriceTracker(blockStore); gasPriceTracker.initializeWindowsFromDB(); return gasPriceTracker; } - private GasPriceTracker(BlockStore blockStore) { - this.blockStore = blockStore; - } - @Override public void onBestBlock(Block block, List receipts) { bestBlockPriceRef.set(block.getMinimumGasPrice()); @@ -109,20 +109,20 @@ private void onTransaction(Transaction tx) { public synchronized Coin getGasPrice() { if (txWindow[0] == null) { // for some reason, not filled yet (i.e. not enough blocks on DB) return defaultPrice; - } else { - if (lastVal == null) { - Coin[] values = Arrays.copyOf(txWindow, TX_WINDOW_SIZE); - Arrays.sort(values); - lastVal = values[values.length / 4]; // 25% percentile - } - - Coin bestBlockPrice = bestBlockPriceRef.get(); - if (bestBlockPrice == null) { - return lastVal; - } else { - return Coin.max(lastVal, bestBlockPrice.multiply(BI_11).divide(BI_10)); - } } + + if (lastVal == null) { + Coin[] values = Arrays.copyOf(txWindow, TX_WINDOW_SIZE); + Arrays.sort(values); + lastVal = values[values.length / 4]; // 25% percentile + } + + Coin bestBlockPrice = bestBlockPriceRef.get(); + if (bestBlockPrice == null) { + return lastVal; + } + + return Coin.max(lastVal, bestBlockPrice.multiply(BI_11).divide(BI_10)); } public synchronized boolean isFeeMarketWorking() { diff --git a/rskj-core/src/test/java/co/rsk/config/RskSystemPropertiesTest.java b/rskj-core/src/test/java/co/rsk/config/RskSystemPropertiesTest.java index db0dda8720c..93ad1a41983 100644 --- a/rskj-core/src/test/java/co/rsk/config/RskSystemPropertiesTest.java +++ b/rskj-core/src/test/java/co/rsk/config/RskSystemPropertiesTest.java @@ -53,6 +53,7 @@ void defaultValues() { assertEquals(0, config.minerMinGasPrice()); assertEquals(0, config.minerGasUnitInDollars(), 0.001); assertEquals(0, config.minerMinFeesNotifyInDollars(), 0.001); + assertEquals(1.1, config.getMinGasPriceMultiplier()); assertFalse(config.getIsHeartBeatEnabled()); } diff --git a/rskj-core/src/test/java/co/rsk/mine/MinerServerTest.java b/rskj-core/src/test/java/co/rsk/mine/MinerServerTest.java index 66bd0194196..cdca5ceea1b 100644 --- a/rskj-core/src/test/java/co/rsk/mine/MinerServerTest.java +++ b/rskj-core/src/test/java/co/rsk/mine/MinerServerTest.java @@ -190,35 +190,35 @@ void submitBitcoinBlockTwoTags() { MinerClock clock = new MinerClock(true, Clock.systemUTC()); MinerServer minerServer = makeMinerServer(ethereumImpl, unclesValidationRule, clock); try { - byte[] extraData = ByteBuffer.allocate(4).putInt(1).array(); - minerServer.setExtraData(extraData); - minerServer.start(); - MinerWork work = minerServer.getWork(); + byte[] extraData = ByteBuffer.allocate(4).putInt(1).array(); + minerServer.setExtraData(extraData); + minerServer.start(); + MinerWork work = minerServer.getWork(); - extraData = ByteBuffer.allocate(4).putInt(2).array(); - minerServer.setExtraData(extraData); - minerServer.buildBlockToMine(false); - MinerWork work2 = minerServer.getWork(); // only the tag is used - Assertions.assertNotEquals(work2.getBlockHashForMergedMining(),work.getBlockHashForMergedMining()); + extraData = ByteBuffer.allocate(4).putInt(2).array(); + minerServer.setExtraData(extraData); + minerServer.buildBlockToMine(false); + MinerWork work2 = minerServer.getWork(); // only the tag is used + Assertions.assertNotEquals(work2.getBlockHashForMergedMining(), work.getBlockHashForMergedMining()); - BtcBlock bitcoinMergedMiningBlock = getMergedMiningBlockWithTwoTags(work,work2); + BtcBlock bitcoinMergedMiningBlock = getMergedMiningBlockWithTwoTags(work, work2); - findNonce(work, bitcoinMergedMiningBlock); - SubmitBlockResult result; - result = minerServer.submitBitcoinBlock(work2.getBlockHashForMergedMining(), bitcoinMergedMiningBlock,true); + findNonce(work, bitcoinMergedMiningBlock); + SubmitBlockResult result; + result = minerServer.submitBitcoinBlock(work2.getBlockHashForMergedMining(), bitcoinMergedMiningBlock, true); - Assertions.assertEquals("OK", result.getStatus()); - Assertions.assertNotNull(result.getBlockInfo()); - Assertions.assertEquals("0x1", result.getBlockInfo().getBlockIncludedHeight()); - Assertions.assertEquals("0x494d504f525445445f42455354", result.getBlockInfo().getBlockImportedResult()); + Assertions.assertEquals("OK", result.getStatus()); + Assertions.assertNotNull(result.getBlockInfo()); + Assertions.assertEquals("0x1", result.getBlockInfo().getBlockIncludedHeight()); + Assertions.assertEquals("0x494d504f525445445f42455354", result.getBlockInfo().getBlockImportedResult()); - // Submit again the save PoW for a different header - result = minerServer.submitBitcoinBlock(work.getBlockHashForMergedMining(), bitcoinMergedMiningBlock,false); + // Submit again the save PoW for a different header + result = minerServer.submitBitcoinBlock(work.getBlockHashForMergedMining(), bitcoinMergedMiningBlock, false); - Assertions.assertEquals("ERROR", result.getStatus()); + Assertions.assertEquals("ERROR", result.getStatus()); - verify(ethereumImpl, times(1)).addNewMinedBlock(any()); + verify(ethereumImpl, times(1)).addNewMinedBlock(any()); } finally { minerServer.stop(); } @@ -408,9 +408,8 @@ void workWithNoTransactionsZeroFees() { minerServer.start(); try { - MinerWork work = minerServer.getWork(); - - assertEquals("0", work.getFeesPaidToMiner()); + MinerWork work = minerServer.getWork(); + assertEquals("0", work.getFeesPaidToMiner()); } finally { minerServer.stop(); } @@ -729,7 +728,14 @@ void onBestBlockBuildBlockToMine() { void onNewTxBuildBlockToMine() throws InterruptedException { // prepare for miner server - Ethereum ethereum = spy(new EthereumImpl(null, null, compositeEthereumListener, standardBlockchain, mock(GasPriceTracker.class)) ); + Ethereum ethereum = spy(new EthereumImpl( + null, + null, + compositeEthereumListener, + standardBlockchain, + mock(GasPriceTracker.class), + rskTestContext.getRskSystemProperties().getMinGasPriceMultiplier()) + ); doReturn(ImportResult.IMPORTED_BEST).when(ethereum).addNewMinedBlock(any()); BlockUnclesValidationRule unclesValidationRule = mock(BlockUnclesValidationRule.class); @@ -741,7 +747,7 @@ void onNewTxBuildBlockToMine() throws InterruptedException { when(blockProcessor.hasBetterBlockToSync()).thenReturn(false); // create the transaction - World world = new World((BlockChainImpl) standardBlockchain, blockStore, rskTestContext.getReceiptStore(), rskTestContext.getTrieStore(), repository, transactionPool, (Genesis)null); + World world = new World((BlockChainImpl) standardBlockchain, blockStore, rskTestContext.getReceiptStore(), rskTestContext.getTrieStore(), repository, transactionPool, (Genesis) null); Account sender = new AccountBuilder(world).name("sender").balance(new Coin(BigInteger.valueOf(2000))).build(); Account receiver = new AccountBuilder(world).name("receiver").build(); diff --git a/rskj-core/src/test/java/co/rsk/mine/TransactionModuleTest.java b/rskj-core/src/test/java/co/rsk/mine/TransactionModuleTest.java index c765662032f..8e1aff4e871 100644 --- a/rskj-core/src/test/java/co/rsk/mine/TransactionModuleTest.java +++ b/rskj-core/src/test/java/co/rsk/mine/TransactionModuleTest.java @@ -580,7 +580,8 @@ private Web3Impl internalCreateEnvironment(Blockchain blockchain, transactionGateway, compositeEthereumListener, blockchain, - GasPriceTracker.create(blockStore) + GasPriceTracker.create(blockStore), + config.getMinGasPriceMultiplier() ); MinerClock minerClock = new MinerClock(true, Clock.systemUTC()); this.transactionExecutorFactory = transactionExecutorFactory; diff --git a/rskj-core/src/test/java/org/ethereum/facade/EthereumImplTest.java b/rskj-core/src/test/java/org/ethereum/facade/EthereumImplTest.java new file mode 100644 index 00000000000..1502e7069cc --- /dev/null +++ b/rskj-core/src/test/java/org/ethereum/facade/EthereumImplTest.java @@ -0,0 +1,63 @@ +/* + * This file is part of RskJ + * Copyright (C) 2023 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package org.ethereum.facade; + +import co.rsk.core.Coin; +import org.ethereum.core.Block; +import org.ethereum.core.Blockchain; +import org.ethereum.listener.CompositeEthereumListener; +import org.ethereum.listener.GasPriceTracker; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class EthereumImplTest { + + + @Test + void getGasPrice_returns_GasPriceTrackerValue_when_feeMarketWorking_is_true() { + GasPriceTracker gasPriceTracker = mock(GasPriceTracker.class); + + when(gasPriceTracker.isFeeMarketWorking()).thenReturn(true); + when(gasPriceTracker.getGasPrice()).thenReturn(Coin.valueOf(10)); + + Ethereum ethereum = new EthereumImpl(null, null, new CompositeEthereumListener(), null, gasPriceTracker, 1); + Coin price = ethereum.getGasPrice(); + + assertEquals(10, price.asBigInteger().intValue()); + } + + @Test + void getGasPrice_returns_correctedBestBlockValue_when_feeMarketWorking_is_false() { + GasPriceTracker gasPriceTracker = mock(GasPriceTracker.class); + Blockchain blockchain = mock(Blockchain.class); + double minGasPriceMultiplier = 1.2; + Block bestBlock = mock(Block.class); + + when(gasPriceTracker.isFeeMarketWorking()).thenReturn(false); + when(bestBlock.getMinimumGasPrice()).thenReturn(Coin.valueOf(10)); + when(blockchain.getBestBlock()).thenReturn(bestBlock); + + Ethereum ethereum = new EthereumImpl(null, null, new CompositeEthereumListener(), blockchain, gasPriceTracker, minGasPriceMultiplier); + Coin price = ethereum.getGasPrice(); + + assertEquals(12, price.asBigInteger().intValue()); + } +} \ No newline at end of file