Skip to content

Commit

Permalink
Adding GasWeighted calculator and extracted legacy calculation to be …
Browse files Browse the repository at this point in the history
…able to choose which one could be used.
  • Loading branch information
asoto-iov committed Jan 4, 2024
1 parent f424db2 commit 4a0e9e2
Show file tree
Hide file tree
Showing 9 changed files with 387 additions and 41 deletions.
5 changes: 3 additions & 2 deletions 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.GasCalculator;
import org.ethereum.listener.GasPriceTracker;
import org.ethereum.net.EthereumChannelInitializerFactory;
import org.ethereum.net.NodeManager;
Expand Down Expand Up @@ -554,9 +555,9 @@ public synchronized Ethereum getRsk() {

public GasPriceTracker getGasPriceTracker() {
checkIfNotClosed();

GasCalculator.GasCalculatorType calculatorType = getRskSystemProperties().getGasCalculatorType();
if (this.gasPriceTracker == null) {
this.gasPriceTracker = GasPriceTracker.create(getBlockStore());
this.gasPriceTracker = GasPriceTracker.create(getBlockStore(), calculatorType);
}
return this.gasPriceTracker;
}
Expand Down
12 changes: 12 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 @@ -29,6 +29,7 @@
import org.ethereum.core.Account;
import org.ethereum.crypto.ECKey;
import org.ethereum.crypto.HashUtil;
import org.ethereum.listener.GasCalculator;

import javax.annotation.Nullable;
import java.nio.charset.StandardCharsets;
Expand Down Expand Up @@ -482,6 +483,17 @@ public double getTopBest() {
return value;
}

public GasCalculator.GasCalculatorType getGasCalculatorType() {
String value = configFromFiles.getString("miner.gasPriceCalculatorType");
if (value == null || value.isEmpty()) {
return GasCalculator.GasCalculatorType.LEGACY;
}
GasCalculator.GasCalculatorType gasCalculatorType = GasCalculator.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
32 changes: 32 additions & 0 deletions rskj-core/src/main/java/org/ethereum/listener/GasCalculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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 GasCalculator {
public enum GasCalculatorType {
LEGACY,
WEIGHTED;

public static GasCalculatorType fromString(String type) {
if (type == null) {
return null;
}
switch (type.toLowerCase()) {
case "weighted":
return WEIGHTED;
case "legacy":
return LEGACY;
default:
return null;
}
}
}

Optional<Coin> getGasPrice();
void onBlock(Block block, List<TransactionReceipt> receipts);
}
74 changes: 36 additions & 38 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 @@ -54,26 +53,46 @@ public class GasPriceTracker extends EthereumListenerAdapter {
private static final BigInteger BI_10 = BigInteger.valueOf(10);
private static final BigInteger BI_11 = BigInteger.valueOf(11);

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 Coin defaultPrice = Coin.valueOf(20_000_000_000L);
private int txIdx = TX_WINDOW_SIZE - 1;

private int blockIdx = 0;

private Coin lastVal;
private final GasCalculator gasCalculator;

private GasPriceTracker(BlockStore blockStore) {
private GasPriceTracker(BlockStore blockStore, GasCalculator gasCalculator) {
this.blockStore = blockStore;
this.gasCalculator = gasCalculator;
}

public static GasPriceTracker create(BlockStore blockStore, GasCalculator.GasCalculatorType gasCalculatorType) {
GasCalculator gasCal;
switch (gasCalculatorType) {
case WEIGHTED:
gasCal = new GasWeightedCalc();
break;
case LEGACY:
gasCal = new LegacyGasCalculator();
break;
default:
throw new IllegalArgumentException("Unknown gas calculator type: " + gasCalculatorType);
}
GasPriceTracker gasPriceTracker = new GasPriceTracker(blockStore, gasCal);
gasPriceTracker.initializeWindowsFromDB();

return gasPriceTracker;
}

/**
* @deprecated Use {@link #create(BlockStore, GasCalculator.GasCalculatorType)} instead.
*/
@Deprecated
public static GasPriceTracker create(BlockStore blockStore) {
GasPriceTracker gasPriceTracker = new GasPriceTracker(blockStore);
//Will be using the legacy gas calculator as default option
GasPriceTracker gasPriceTracker = new GasPriceTracker(blockStore, new LegacyGasCalculator());
gasPriceTracker.initializeWindowsFromDB();
return gasPriceTracker;
}
Expand All @@ -91,38 +110,25 @@ public synchronized void onBlock(Block block, List<TransactionReceipt> receipts)

trackBlockCompleteness(block);

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

gasCalculator.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 = gasCalculator.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: {}", gasCalculator.getGasPrice());

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, bestBlockPrice.multiply(BI_11).divide(BI_10));
return Coin.max(gasPriceResult.get(), bestBlockPrice.multiply(BI_11).divide(BI_10));
}

public synchronized boolean isFeeMarketWorking() {
Expand Down Expand Up @@ -174,14 +180,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 Down
105 changes: 105 additions & 0 deletions rskj-core/src/main/java/org/ethereum/listener/GasWeightedCalc.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* This file is part of RskJ
* Copyright (C) 2017 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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigDecimal;
import java.util.*;

public class GasWeightedCalc implements GasCalculator {
private static final Logger logger = LoggerFactory.getLogger("gaspricetracker");
private static final int WINDOW_SIZE = 512;
private final Deque<GasEntry> gasWindow = new ArrayDeque<>(WINDOW_SIZE);
private final Map<Coin, Long> windowMap = new HashMap<>();
private int txCount = 0;
private Coin cachedGasPrice = null;

public synchronized void onBlock(Block block, List<TransactionReceipt> receipts) {

Check notice

Code scanning / CodeQL

Missing Override annotation Note

This method overrides
GasCalculator.onBlock
; it is advisable to add an Override annotation.
for(TransactionReceipt receipt : receipts) {
if (!(receipt.getTransaction() instanceof RemascTransaction)) {
addTx(receipt.getTransaction(), new Coin(receipt.getGasUsed()).asBigInteger().longValue());
}
}
}

private void addTx(Transaction tx, long gasUsed) {
txCount++;

Coin gasPrice = tx.getGasPrice();

if (gasWindow.size() == WINDOW_SIZE) {
GasEntry entry = gasWindow.removeFirst();
long value = windowMap.get(entry.gasPrice) - entry.gasUsed;
if (value > 0) {
windowMap.put(entry.gasPrice, value);
} else {
windowMap.remove(entry.gasPrice);
}
}

gasWindow.add(new GasEntry(gasPrice, gasUsed));
windowMap.merge(gasPrice, gasUsed, Long::sum);

if (txCount >= WINDOW_SIZE) {
txCount = 0; // Reset the count
cachedGasPrice = calculateGasPrice();
logger.info("Updated gas price -> {}",cachedGasPrice);
}
}

private synchronized Coin calculateGasPrice() {
double weightedSum = 0;
double totalGasUsed = 0;
for(Map.Entry<Coin,Long> entry : windowMap.entrySet()) {
weightedSum += entry.getKey().asBigInteger().doubleValue() * entry.getValue();
totalGasUsed += entry.getValue();
}

if (totalGasUsed > 0) {
double result = weightedSum / totalGasUsed;
return new Coin(BigDecimal.valueOf(result).toBigInteger());
}
return null;
}

public synchronized Optional<Coin> getGasPrice() {

Check notice

Code scanning / CodeQL

Missing Override annotation Note

This method overrides
GasCalculator.getGasPrice
; it is advisable to add an Override annotation.
if(cachedGasPrice == null) {
cachedGasPrice = calculateGasPrice();
}
return cachedGasPrice == null ? Optional.empty() : Optional.of(cachedGasPrice);
}

static class GasEntry {
protected Coin gasPrice;
protected long gasUsed;

GasEntry(Coin gasPrice, long gasUsed) {
this.gasPrice = gasPrice;
this.gasUsed = gasUsed;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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 LegacyGasCalculator implements GasCalculator {
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;

public synchronized Optional<Coin> getGasPrice() {

Check notice

Code scanning / CodeQL

Missing Override annotation Note

This method overrides
GasCalculator.getGasPrice
; it is advisable to add an Override annotation.
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 void onBlock(Block block, List<TransactionReceipt> receipts) {
onBlock(block.getTransactionsList());
}

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

}
1 change: 1 addition & 0 deletions rskj-core/src/main/resources/expected.conf
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ wallet = {
miner = {
gasUnitInDollars = <double>
minGasPrice = <minGasPrice>
gasPriceCalculatorType = <gasPriceCalculatorType>
server = {
enabled = <enabled>
isFixedClock = <isFixedClock>
Expand Down
2 changes: 1 addition & 1 deletion rskj-core/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ peer {
miner {
# The default gas price
minGasPrice = 0

gasPriceCalculatorType = LEGACY
server {
enabled = false
isFixedClock = false
Expand Down
Loading

0 comments on commit 4a0e9e2

Please sign in to comment.