diff --git a/rskj-core/src/main/java/co/rsk/RskContext.java b/rskj-core/src/main/java/co/rsk/RskContext.java index 2d6f24fa06c..441912dd939 100644 --- a/rskj-core/src/main/java/co/rsk/RskContext.java +++ b/rskj-core/src/main/java/co/rsk/RskContext.java @@ -555,8 +555,10 @@ public synchronized Ethereum getRsk() { public GasPriceTracker getGasPriceTracker() { checkIfNotClosed(); + double gasPriceMultiplier = getRskSystemProperties().gasPriceMultiplier(); + if (this.gasPriceTracker == null) { - this.gasPriceTracker = GasPriceTracker.create(getBlockStore()); + this.gasPriceTracker = GasPriceTracker.create(getBlockStore(), gasPriceMultiplier); } return this.gasPriceTracker; } diff --git a/rskj-core/src/main/java/co/rsk/config/RskSystemProperties.java b/rskj-core/src/main/java/co/rsk/config/RskSystemProperties.java index ad411a32043..c1dde0b6d68 100644 --- a/rskj-core/src/main/java/co/rsk/config/RskSystemProperties.java +++ b/rskj-core/src/main/java/co/rsk/config/RskSystemProperties.java @@ -54,6 +54,7 @@ public class RskSystemProperties extends SystemProperties { private static final String RPC_MODULES_PATH = "rpc.modules"; private static final String RPC_ETH_GET_LOGS_MAX_BLOCKS_TO_QUERY = "rpc.logs.maxBlocksToQuery"; private static final String RPC_ETH_GET_LOGS_MAX_LOGS_TO_RETURN = "rpc.logs.maxLogsToReturn"; + private static final String RPC_GAS_PRICE_MULTIPLIER_CONFIG = "rpc.gasPriceMultiplier"; private static final int CHUNK_SIZE = 192; @@ -185,6 +186,16 @@ public boolean isWalletEnabled() { return getBoolean("wallet.enabled", false); } + public double gasPriceMultiplier() { + double gasPriceMultiplier = getDouble(RPC_GAS_PRICE_MULTIPLIER_CONFIG, 1.1); + + if(gasPriceMultiplier >= 0) { + return gasPriceMultiplier; + } else { + throw new RskConfigurationException(RPC_GAS_PRICE_MULTIPLIER_CONFIG + " cannot be a negative number"); + } + } + public List<WalletAccount> walletAccounts() { if (!configFromFiles.hasPath("wallet.accounts")) { return Collections.emptyList(); 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 964a6f30015..d9a29d489c4 100644 --- a/rskj-core/src/main/java/org/ethereum/listener/GasPriceTracker.java +++ b/rskj-core/src/main/java/org/ethereum/listener/GasPriceTracker.java @@ -29,7 +29,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.math.BigInteger; +import java.math.BigDecimal; import java.util.*; import java.util.concurrent.atomic.AtomicReference; @@ -51,8 +51,7 @@ public class GasPriceTracker extends EthereumListenerAdapter { private static final double BLOCK_COMPLETION_PERCENT_FOR_FEE_MARKET_WORKING = 0.9; - private static final BigInteger BI_10 = BigInteger.valueOf(10); - private static final BigInteger BI_11 = BigInteger.valueOf(11); + private static final double DEFAULT_GAS_PRICE_MULTIPLIER = 1.1; private final Coin[] txWindow = new Coin[TX_WINDOW_SIZE]; @@ -60,6 +59,7 @@ public class GasPriceTracker extends EthereumListenerAdapter { private final AtomicReference<Coin> bestBlockPriceRef = new AtomicReference<>(); private final BlockStore blockStore; + private final double gasPriceMultiplier; private Coin defaultPrice = Coin.valueOf(20_000_000_000L); private int txIdx = TX_WINDOW_SIZE - 1; @@ -68,12 +68,17 @@ public class GasPriceTracker extends EthereumListenerAdapter { private Coin lastVal; - private GasPriceTracker(BlockStore blockStore) { + private GasPriceTracker(BlockStore blockStore, Double configMultiplier) { this.blockStore = blockStore; + this.gasPriceMultiplier = configMultiplier; } public static GasPriceTracker create(BlockStore blockStore) { - GasPriceTracker gasPriceTracker = new GasPriceTracker(blockStore); + return create(blockStore, DEFAULT_GAS_PRICE_MULTIPLIER); + } + + public static GasPriceTracker create(BlockStore blockStore, Double configMultiplier) { + GasPriceTracker gasPriceTracker = new GasPriceTracker(blockStore, configMultiplier); gasPriceTracker.initializeWindowsFromDB(); return gasPriceTracker; } @@ -122,7 +127,8 @@ public synchronized Coin getGasPrice() { return lastVal; } - return Coin.max(lastVal, bestBlockPrice.multiply(BI_11).divide(BI_10)); + return Coin.max(lastVal, new Coin(new BigDecimal(bestBlockPrice.asBigInteger()) + .multiply(BigDecimal.valueOf(gasPriceMultiplier)).toBigInteger())); } public synchronized boolean isFeeMarketWorking() { diff --git a/rskj-core/src/main/java/org/ethereum/util/Utils.java b/rskj-core/src/main/java/org/ethereum/util/Utils.java index a703c2d131c..a2d23853a51 100644 --- a/rskj-core/src/main/java/org/ethereum/util/Utils.java +++ b/rskj-core/src/main/java/org/ethereum/util/Utils.java @@ -21,7 +21,6 @@ import org.bouncycastle.util.encoders.DecoderException; import org.bouncycastle.util.encoders.Hex; - import java.lang.reflect.Array; import java.math.BigInteger; import java.nio.charset.StandardCharsets; diff --git a/rskj-core/src/main/resources/expected.conf b/rskj-core/src/main/resources/expected.conf index dec928794e5..58f009898b8 100644 --- a/rskj-core/src/main/resources/expected.conf +++ b/rskj-core/src/main/resources/expected.conf @@ -248,6 +248,7 @@ rpc = { callGasCap = <number> timeout = <number> maxResponseSize = <number> + gasPriceMultiplier = <gasPriceMultiplier> providers = { web = { cors = <cors> diff --git a/rskj-core/src/main/resources/reference.conf b/rskj-core/src/main/resources/reference.conf index 39823139df6..3f689a5ed4a 100644 --- a/rskj-core/src/main/resources/reference.conf +++ b/rskj-core/src/main/resources/reference.conf @@ -330,6 +330,10 @@ rpc { # Maximum Response size allowed in bytes. If value is 0 then there is no limit. maxResponseSize = 0 + # This property can be set to a numeric value by the node operator to over write the percentage for the + # gas price multiplier used to calculate the gas price returned by eth_gasPrice + gasPriceMultiplier = 1.1 + providers { web { cors = "localhost" 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 93ad1a41983..f38c0334cd4 100644 --- a/rskj-core/src/test/java/co/rsk/config/RskSystemPropertiesTest.java +++ b/rskj-core/src/test/java/co/rsk/config/RskSystemPropertiesTest.java @@ -22,7 +22,9 @@ import co.rsk.cli.RskCli; import co.rsk.rpc.ModuleDescription; import com.typesafe.config.Config; +import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigFactory; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; @@ -223,4 +225,40 @@ void testGetRpcModulesWithObject() { assertEquals(9000, netModule.getTimeout()); assertEquals(30000, netModule.getMethodTimeout("eth_getBlockByHash")); } + + @Test + void testGasPriceMultiplier() { + assertEquals(1.05, config.gasPriceMultiplier()); + } + + @Test + void testGasPriceMultiplierWithNull() { + // Set miner.gasPriceMultiplier to null which yields the same result as not finding the path + TestSystemProperties testSystemProperties = new TestSystemProperties(rawConfig -> + ConfigFactory.parseString("{" + + "rpc.gasPriceMultiplier = null" + + " }").withFallback(rawConfig)); + + assertEquals(1.1, testSystemProperties.gasPriceMultiplier()); + } + + @Test + void testGasPriceMultiplierThrowsErrorForInvalidType() { + TestSystemProperties testSystemProperties = new TestSystemProperties(rawConfig -> + ConfigFactory.parseString("{" + + "rpc.gasPriceMultiplier = invalid" + + " }").withFallback(rawConfig)); + + Assertions.assertThrows(ConfigException.WrongType.class, testSystemProperties::gasPriceMultiplier); + } + + @Test + void testGasPriceMultiplierThrowsErrorForNegativeValue() { + TestSystemProperties testSystemProperties = new TestSystemProperties(rawConfig -> + ConfigFactory.parseString("{" + + "rpc.gasPriceMultiplier = -1" + + " }").withFallback(rawConfig)); + + Assertions.assertThrows(RskConfigurationException.class, testSystemProperties::gasPriceMultiplier); + } } diff --git a/rskj-core/src/test/java/org/ethereum/facade/EthereumImplTest.java b/rskj-core/src/test/java/org/ethereum/facade/EthereumImplTest.java index 1502e7069cc..033023e22c5 100644 --- a/rskj-core/src/test/java/org/ethereum/facade/EthereumImplTest.java +++ b/rskj-core/src/test/java/org/ethereum/facade/EthereumImplTest.java @@ -30,7 +30,6 @@ class EthereumImplTest { - @Test void getGasPrice_returns_GasPriceTrackerValue_when_feeMarketWorking_is_true() { GasPriceTracker gasPriceTracker = mock(GasPriceTracker.class); @@ -60,4 +59,4 @@ void getGasPrice_returns_correctedBestBlockValue_when_feeMarketWorking_is_false( assertEquals(12, price.asBigInteger().intValue()); } -} \ No newline at end of file +} diff --git a/rskj-core/src/test/java/org/ethereum/listener/GasPriceTrackerTest.java b/rskj-core/src/test/java/org/ethereum/listener/GasPriceTrackerTest.java index 35e9134cd6d..07382c02356 100644 --- a/rskj-core/src/test/java/org/ethereum/listener/GasPriceTrackerTest.java +++ b/rskj-core/src/test/java/org/ethereum/listener/GasPriceTrackerTest.java @@ -150,6 +150,21 @@ void getGasPrice_PriceWindowFilled_BestBlockReceivedWithGreaterPrice_ReturnsBest assertEquals(Coin.valueOf(55_000_000_000L), actualResult); } + @Test + void getGasPrice_PriceWindowFilled_BestBlockReceivedWithGreaterPrice_GasPriceMultiplierOverWritten_ReturnsBestBlockAdjustedPriceWithNewBuffer() { + GasPriceTracker gasPriceTracker = GasPriceTracker.create(blockStore, 1.05); + + Block bestBlock = makeBlock(Coin.valueOf(50_000_000_000L), 0, i -> null); + Block block = makeBlock(Coin.valueOf(30_000_000_000L), TOTAL_SLOTS, i -> makeTx(Coin.valueOf(40_000_000_000L))); + + gasPriceTracker.onBestBlock(bestBlock, Collections.emptyList()); + gasPriceTracker.onBlock(block, Collections.emptyList()); + + Coin actualResult = gasPriceTracker.getGasPrice(); + + assertEquals(Coin.valueOf(52_500_000_000L), actualResult); + } + @Test void isFeeMarketWorking_falseWhenNotEnoughBlocks() { GasPriceTracker gasPriceTracker = GasPriceTracker.create(blockStore); diff --git a/rskj-core/src/test/resources/test-rskj.conf b/rskj-core/src/test/resources/test-rskj.conf index 1942a23e680..c67a567701f 100644 --- a/rskj-core/src/test/resources/test-rskj.conf +++ b/rskj-core/src/test/resources/test-rskj.conf @@ -210,6 +210,7 @@ sync { } rpc = { + gasPriceMultiplier = 1.05 callGasCap: 50000000, timeout: 0, maxResponseSize: 0,