Skip to content

Commit

Permalink
Merge pull request #2215 from rsksmart/update_gasPrice_tracker
Browse files Browse the repository at this point in the history
Weighted percentile for gasPrice calculation
  • Loading branch information
Vovchyk authored Mar 5, 2024
2 parents 6dfdb03 + ad4451b commit 64faf03
Show file tree
Hide file tree
Showing 13 changed files with 679 additions and 45 deletions.
4 changes: 3 additions & 1 deletion rskj-core/src/main/java/co/rsk/RskContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
import org.ethereum.facade.Ethereum;
import org.ethereum.facade.EthereumImpl;
import org.ethereum.listener.CompositeEthereumListener;
import org.ethereum.listener.GasPriceCalculator;
import org.ethereum.listener.GasPriceTracker;
import org.ethereum.net.EthereumChannelInitializerFactory;
import org.ethereum.net.NodeManager;
Expand Down Expand Up @@ -558,7 +559,8 @@ public GasPriceTracker getGasPriceTracker() {
double gasPriceMultiplier = getRskSystemProperties().gasPriceMultiplier();

if (this.gasPriceTracker == null) {
this.gasPriceTracker = GasPriceTracker.create(getBlockStore(), gasPriceMultiplier);
GasPriceCalculator.GasCalculatorType calculatorType = getRskSystemProperties().getGasCalculatorType();
this.gasPriceTracker = GasPriceTracker.create(getBlockStore(), gasPriceMultiplier, calculatorType);
}
return this.gasPriceTracker;
}
Expand Down
15 changes: 15 additions & 0 deletions rskj-core/src/main/java/co/rsk/config/RskSystemProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.ethereum.core.Account;
import org.ethereum.crypto.ECKey;
import org.ethereum.crypto.HashUtil;
import org.ethereum.listener.GasPriceCalculator;

import javax.annotation.Nullable;
import java.nio.charset.StandardCharsets;
Expand Down Expand Up @@ -58,6 +59,8 @@ 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";
public static final String TX_GAS_PRICE_CALCULATOR_TYPE = "transaction.gasPriceCalculatorType";

private static final String RPC_GAS_PRICE_MULTIPLIER_CONFIG = "rpc.gasPriceMultiplier";
private static final String DISCOVERY_BUCKET_SIZE = "peer.discovery.bucketSize";

Expand Down Expand Up @@ -506,6 +509,18 @@ public double getTopBest() {
return value;
}

public GasPriceCalculator.GasCalculatorType getGasCalculatorType() {
String value = configFromFiles.getString(TX_GAS_PRICE_CALCULATOR_TYPE);
if (value == null || value.isEmpty()) {
return GasPriceCalculator.GasCalculatorType.PLAIN_PERCENTILE;
}
GasPriceCalculator.GasCalculatorType gasCalculatorType = GasPriceCalculator.GasCalculatorType.fromString(value);
if(gasCalculatorType == null) {
throw new RskConfigurationException("Invalid gasPriceCalculatorType: " + value);
}
return gasCalculatorType;
}

private void fetchMethodTimeout(Config configElement, Map<String, Long> methodTimeoutMap) {
configElement.getObject("methods.timeout")
.unwrapped()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* This file is part of RskJ
* Copyright (C) 2024 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.listener;

import co.rsk.core.Coin;
import org.ethereum.core.Block;
import org.ethereum.core.TransactionReceipt;

import java.util.List;
import java.util.Optional;

public interface GasPriceCalculator {
public enum GasCalculatorType {
PLAIN_PERCENTILE,
WEIGHTED_PERCENTILE;

public static GasCalculatorType fromString(String type) {
if (type == null) {
return null;
}
switch (type.toLowerCase()) {
case "weighted_percentile":
return WEIGHTED_PERCENTILE;
case "plain_percentile":
return PLAIN_PERCENTILE;
default:
return null;
}
}
}

Optional<Coin> getGasPrice();
void onBlock(Block block, List<TransactionReceipt> receipts);

GasCalculatorType getType();
}
82 changes: 41 additions & 41 deletions rskj-core/src/main/java/org/ethereum/listener/GasPriceTracker.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import co.rsk.crypto.Keccak256;
import co.rsk.remasc.RemascTransaction;
import org.ethereum.core.Block;
import org.ethereum.core.Transaction;
import org.ethereum.core.TransactionReceipt;
import org.ethereum.db.BlockStore;
import org.slf4j.Logger;
Expand All @@ -35,10 +34,10 @@

/**
* Calculates a 'reasonable' Gas price based on statistics of the latest transaction's Gas prices
*
* <p>
* Normally the price returned should be sufficient to execute a transaction since ~25% of the latest
* transactions were executed at this or lower price.
*
* <p>
* Created by Anton Nashatyrev on 22.09.2015.
*/
public class GasPriceTracker extends EthereumListenerAdapter {
Expand All @@ -53,36 +52,54 @@ public class GasPriceTracker extends EthereumListenerAdapter {

private static final double DEFAULT_GAS_PRICE_MULTIPLIER = 1.1;

private final Coin[] txWindow = new Coin[TX_WINDOW_SIZE];

private final Double[] blockWindow = new Double[BLOCK_WINDOW_SIZE];

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;

private int blockIdx = 0;

private Coin lastVal;
private final GasPriceCalculator gasPriceCalculator;

private GasPriceTracker(BlockStore blockStore, Double configMultiplier) {
private GasPriceTracker(BlockStore blockStore, GasPriceCalculator gasPriceCalculator, Double configMultiplier) {
this.blockStore = blockStore;
this.gasPriceCalculator = gasPriceCalculator;
this.gasPriceMultiplier = configMultiplier;
}

public static GasPriceTracker create(BlockStore blockStore) {
return create(blockStore, DEFAULT_GAS_PRICE_MULTIPLIER);
public static GasPriceTracker create(BlockStore blockStore, GasPriceCalculator.GasCalculatorType gasCalculatorType) {
return create(blockStore, DEFAULT_GAS_PRICE_MULTIPLIER, gasCalculatorType);
}

public static GasPriceTracker create(BlockStore blockStore, Double configMultiplier) {
GasPriceTracker gasPriceTracker = new GasPriceTracker(blockStore, configMultiplier);
public static GasPriceTracker create(BlockStore blockStore, Double configMultiplier, GasPriceCalculator.GasCalculatorType gasCalculatorType) {
GasPriceCalculator gasCal;
switch (gasCalculatorType) {
case WEIGHTED_PERCENTILE:
gasCal = new WeightedPercentileGasPriceCalculator();
break;
case PLAIN_PERCENTILE:
gasCal = new PercentileGasPriceCalculator();
break;
default:
throw new IllegalArgumentException("Unknown gas calculator type: " + gasCalculatorType);
}
GasPriceTracker gasPriceTracker = new GasPriceTracker(blockStore, gasCal, configMultiplier);
gasPriceTracker.initializeWindowsFromDB();

return gasPriceTracker;
}

/**
* @deprecated Use {@link #create(BlockStore, GasPriceCalculator.GasCalculatorType)} instead.
*/
@Deprecated
public static GasPriceTracker create(BlockStore blockStore) {
//Will be using the legacy gas calculator as default option
return GasPriceTracker.create(blockStore, GasPriceCalculator.GasCalculatorType.PLAIN_PERCENTILE);
}

@Override
public void onBestBlock(Block block, List<TransactionReceipt> receipts) {
bestBlockPriceRef.set(block.getMinimumGasPrice());
Expand All @@ -96,38 +113,25 @@ public synchronized void onBlock(Block block, List<TransactionReceipt> receipts)

trackBlockCompleteness(block);

for (Transaction tx : block.getTransactionsList()) {
onTransaction(tx);
}

gasPriceCalculator.onBlock(block, receipts);
logger.trace("End onBlock");
}

private void onTransaction(Transaction tx) {
if (tx instanceof RemascTransaction) {
return;
}

trackGasPrice(tx);
}

public synchronized Coin getGasPrice() {
if (txWindow[0] == null) { // for some reason, not filled yet (i.e. not enough blocks on DB)
Optional<Coin> gasPriceResult = gasPriceCalculator.getGasPrice();
if(!gasPriceResult.isPresent()) {
return defaultPrice;
}

if (lastVal == null) {
Coin[] values = Arrays.copyOf(txWindow, TX_WINDOW_SIZE);
Arrays.sort(values);
lastVal = values[values.length / 4]; // 25% percentile
}
logger.debug("Gas provided by GasWindowCalc: {}", gasPriceResult.get());

Coin bestBlockPrice = bestBlockPriceRef.get();
if (bestBlockPrice == null) {
return lastVal;
logger.debug("Best block price not available, defaulting to {}", gasPriceResult.get());
return gasPriceResult.get();
}

return Coin.max(lastVal, new Coin(new BigDecimal(bestBlockPrice.asBigInteger())
return Coin.max(gasPriceResult.get(), new Coin(new BigDecimal(bestBlockPrice.asBigInteger())
.multiply(BigDecimal.valueOf(gasPriceMultiplier)).toBigInteger()));
}

Expand Down Expand Up @@ -180,14 +184,6 @@ private List<Block> getRequiredBlocksToFillWindowsFromDB() {
return blocks;
}

private void trackGasPrice(Transaction tx) {
if (txIdx == -1) {
txIdx = TX_WINDOW_SIZE - 1;
lastVal = null; // recalculate only 'sometimes'
}
txWindow[txIdx--] = tx.getGasPrice();
}

private void trackBlockCompleteness(Block block) {
double gasUsed = block.getGasUsed();
double gasLimit = block.getGasLimitAsInteger().doubleValue();
Expand All @@ -199,4 +195,8 @@ private void trackBlockCompleteness(Block block) {
blockWindow[blockIdx++] = completeness;
}

public GasPriceCalculator.GasCalculatorType getGasCalculatorType() {
return gasPriceCalculator.getType();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* This file is part of RskJ
* Copyright (C) 2024 RSK Labs Ltd.
* (derived from ethereumJ library, Copyright (c) 2016 <ether.camp>)
*
* 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.listener;

import co.rsk.core.Coin;
import co.rsk.remasc.RemascTransaction;
import org.ethereum.core.Block;
import org.ethereum.core.Transaction;
import org.ethereum.core.TransactionReceipt;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class PercentileGasPriceCalculator implements GasPriceCalculator {
private static final int TX_WINDOW_SIZE = 512;

private final Coin[] txWindow = new Coin[TX_WINDOW_SIZE];
private int txIdx = TX_WINDOW_SIZE - 1;
private Coin lastVal;

@Override
public synchronized Optional<Coin> getGasPrice() {
if (txWindow[0] == null) { // for some reason, not filled yet (i.e. not enough blocks on DB)
return Optional.empty();
} else {
if (lastVal == null) {
Coin[] values = Arrays.copyOf(txWindow, TX_WINDOW_SIZE);
Arrays.sort(values);
lastVal = values[values.length / 4]; // 25% percentile
}
return Optional.of(lastVal);
}
}

@Override
public synchronized void onBlock(Block block, List<TransactionReceipt> receipts) {
onBlock(block.getTransactionsList());
}

@Override
public GasCalculatorType getType() {
return GasCalculatorType.PLAIN_PERCENTILE;
}

private void onBlock(List<Transaction> transactionList) {
for (Transaction tx : transactionList) {
if (!(tx instanceof RemascTransaction)) {
trackGasPrice(tx);
}
}
}

private void trackGasPrice(Transaction tx) {
if (txIdx == -1) {
txIdx = TX_WINDOW_SIZE - 1;
lastVal = null; // recalculate only 'sometimes'
}
txWindow[txIdx--] = tx.getGasPrice();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* This file is part of RskJ
* Copyright (C) 2024 RSK Labs Ltd.
* (derived from ethereumJ library, Copyright (c) 2016 <ether.camp>)
*
* 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.listener;

import co.rsk.core.Coin;

import java.util.Collections;
import java.util.List;

class WeightedPercentileCalc {

Coin calculateWeightedPercentile(float percentile, List<WeightedPercentileGasPriceCalculator.GasEntry> gasEntries) {
if (gasEntries == null || gasEntries.isEmpty()) {
return null;
}

Collections.sort(gasEntries);

double totalWeight = gasEntries.stream().mapToLong(WeightedPercentileGasPriceCalculator.GasEntry::getGasUsed).sum();

double targetWeight = percentile / 100 * totalWeight;


double cumulativeWeight = 0;
for (WeightedPercentileGasPriceCalculator.GasEntry pair : gasEntries) {
cumulativeWeight += pair.getGasUsed();
if (cumulativeWeight >= targetWeight) {
return pair.getGasPrice();
}
}

return null;
}
}
Loading

0 comments on commit 64faf03

Please sign in to comment.