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

Updating how eth_gasPrice is calculated #2176

Merged
merged 3 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion rskj-core/src/main/java/co/rsk/RskContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,8 @@ public synchronized Ethereum getRsk() {
getTransactionGateway(),
getCompositeEthereumListener(),
getBlockchain(),
getGasPriceTracker()
getGasPriceTracker(),
getRskSystemProperties().getMinGasPriceMultiplier()
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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;
Expand Down
12 changes: 10 additions & 2 deletions rskj-core/src/main/java/org/ethereum/facade/EthereumImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.ethereum.net.server.ChannelManager;

import javax.annotation.Nonnull;
import java.math.BigDecimal;

public class EthereumImpl implements Ethereum {

Expand All @@ -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;

Expand All @@ -51,6 +54,7 @@ public EthereumImpl(

this.gasPriceTracker = gasPriceTracker;
compositeEthereumListener.addListener(gasPriceTracker);
this.minGasPriceMultiplier = minGasPriceMultiplier;
}

@Override
Expand Down Expand Up @@ -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());
}
}
34 changes: 17 additions & 17 deletions rskj-core/src/main/java/org/ethereum/listener/GasPriceTracker.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<TransactionReceipt> receipts) {
bestBlockPriceRef.set(block.getMinimumGasPrice());
Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
58 changes: 32 additions & 26 deletions rskj-core/src/test/java/co/rsk/mine/MinerServerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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);
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
63 changes: 63 additions & 0 deletions rskj-core/src/test/java/org/ethereum/facade/EthereumImplTest.java
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
package org.ethereum.facade;
asoto-iov marked this conversation as resolved.
Show resolved Hide resolved

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 {

Check notice

Code scanning / CodeQL

Unused classes and interfaces Note

Unused class: EthereumImplTest is not referenced within this codebase. If not used as an external API it should be removed.


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