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

Weighted percentile for gasPrice calculation #2215

Merged
merged 1 commit into from
Mar 5, 2024
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
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() {
Fixed Show fixed Hide fixed
asoto-iov marked this conversation as resolved.
Show resolved Hide resolved
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;
}
}