Move gas refund calculation from MainnetTransactionProcessor to GasCalculator (#8106)

* move gas refund calculation from MainnetTransactionProcessor to RefundCalculator

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* fix MainnetTransactionProcessorTest

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* add test for FrontierRefundCalculator

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* add test for FrontierRefundCalculator

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* add PragueRefundCalculator dummy

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* move refund logic to gas calculator, delete RefundCalculator

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

---------

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>
This commit is contained in:
daniellehrner
2025-01-14 23:58:35 +01:00
committed by GitHub
parent 8308650ef9
commit 42b26a466d
4 changed files with 143 additions and 14 deletions

View File

@@ -500,11 +500,8 @@ public class MainnetTransactionProcessor {
// Refund the sender by what we should and pay the miner fee (note that we're doing them one
// after the other so that if it is the same account somehow, we end up with the right result)
final long selfDestructRefund =
gasCalculator.getSelfDestructRefundAmount() * initialFrame.getSelfDestructs().size();
final long baseRefundGas =
initialFrame.getGasRefund() + selfDestructRefund + codeDelegationRefund;
final long refundedGas = refunded(transaction, initialFrame.getRemainingGas(), baseRefundGas);
final long refundedGas =
gasCalculator.calculateGasRefund(transaction, initialFrame, codeDelegationRefund);
final Wei refundedWei = transactionGasPrice.multiply(refundedGas);
final Wei balancePriorToRefund = sender.getBalance();
sender.incrementBalance(refundedWei);
@@ -635,15 +632,6 @@ public class MainnetTransactionProcessor {
};
}
protected long refunded(
final Transaction transaction, final long gasRemaining, final long gasRefund) {
// Integer truncation takes care of the floor calculation needed after the divide.
final long maxRefundAllowance =
(transaction.getGasLimit() - gasRemaining) / gasCalculator.getMaxRefundQuotient();
final long refundAllowance = Math.min(maxRefundAllowance, gasRefund);
return gasRemaining + refundAllowance;
}
private String printableStackTraceFromThrowable(final RuntimeException re) {
final StringBuilder builder = new StringBuilder();

View File

@@ -21,6 +21,7 @@ import static org.hyperledger.besu.evm.internal.Words.clampedToLong;
import static org.hyperledger.besu.evm.internal.Words.numWords;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Transaction;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.evm.frame.MessageFrame;
@@ -566,4 +567,25 @@ public class FrontierGasCalculator implements GasCalculator {
public long getMinimumTransactionCost() {
return TX_BASE_COST;
}
@Override
public long calculateGasRefund(
final Transaction transaction,
final MessageFrame initialFrame,
final long codeDelegationRefund) {
final long selfDestructRefund =
getSelfDestructRefundAmount() * initialFrame.getSelfDestructs().size();
final long baseRefundGas =
initialFrame.getGasRefund() + selfDestructRefund + codeDelegationRefund;
return refunded(transaction, initialFrame.getRemainingGas(), baseRefundGas);
}
private long refunded(
final Transaction transaction, final long gasRemaining, final long gasRefund) {
// Integer truncation takes care of the floor calculation needed after the divide.
final long maxRefundAllowance =
(transaction.getGasLimit() - gasRemaining) / getMaxRefundQuotient();
final long refundAllowance = Math.min(maxRefundAllowance, gasRefund);
return gasRemaining + refundAllowance;
}
}

View File

@@ -16,6 +16,7 @@ package org.hyperledger.besu.evm.gascalculator;
import org.hyperledger.besu.datatypes.AccessListEntry;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Transaction;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.evm.frame.MessageFrame;
@@ -664,4 +665,15 @@ public interface GasCalculator {
default long calculateDelegateCodeGasRefund(final long alreadyExistingAccountSize) {
return 0L;
}
/**
* Calculate the gas refund for a transaction.
*
* @param transaction the transaction
* @param initialFrame the initial frame
* @param codeDelegationRefund the code delegation refund
* @return the gas refund
*/
long calculateGasRefund(
Transaction transaction, MessageFrame initialFrame, long codeDelegationRefund);
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright contributors to Hyperledger Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.evm.gascalculator;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Transaction;
import org.hyperledger.besu.evm.frame.MessageFrame;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.apache.tuweni.bytes.Bytes;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class FrontierGasCalculatorTest {
private final GasCalculator gasCalculator = new FrontierGasCalculator();
@Mock private Transaction transaction;
@Mock private MessageFrame messageFrame;
@Test
void shouldCalculateRefundWithNoSelfDestructs() {
// Arrange
when(messageFrame.getSelfDestructs()).thenReturn(Collections.emptySet());
when(messageFrame.getGasRefund()).thenReturn(1000L);
when(messageFrame.getRemainingGas()).thenReturn(5000L);
when(transaction.getGasLimit()).thenReturn(100000L);
// Act
long refund = gasCalculator.calculateGasRefund(transaction, messageFrame, 500L);
// Assert
assertThat(refund)
.isEqualTo(6500L); // 5000 (remaining) + min(1500 (total refund), 19000 (max allowance))
}
@Test
void shouldCalculateRefundWithMultipleSelfDestructs() {
// Arrange
Set<Address> selfDestructs = new HashSet<>();
selfDestructs.add(Address.wrap(Bytes.random(20)));
selfDestructs.add(Address.wrap(Bytes.random(20)));
when(messageFrame.getSelfDestructs()).thenReturn(selfDestructs);
when(messageFrame.getGasRefund()).thenReturn(1000L);
when(messageFrame.getRemainingGas()).thenReturn(5000L);
when(transaction.getGasLimit()).thenReturn(100000L);
// Act
long refund = gasCalculator.calculateGasRefund(transaction, messageFrame, 500L);
// Assert
assertThat(refund)
.isEqualTo(52500L); // 5000 (remaining) + min(47500 (total refund), 49500 (max allowance))
}
@Test
void shouldRespectMaxRefundAllowance() {
// Arrange
when(messageFrame.getSelfDestructs()).thenReturn(Collections.emptySet());
when(messageFrame.getGasRefund()).thenReturn(100000L);
when(messageFrame.getRemainingGas()).thenReturn(20000L);
when(transaction.getGasLimit()).thenReturn(100000L);
// Act
long refund = gasCalculator.calculateGasRefund(transaction, messageFrame, 1000L);
// Assert
assertThat(refund)
.isEqualTo(60000L); // 20000 (remaining) + min(101000 (total refund), 40000 (max allowance))
}
@Test
void shouldHandleZeroValuesCorrectly() {
// Arrange
when(messageFrame.getSelfDestructs()).thenReturn(Collections.emptySet());
when(messageFrame.getGasRefund()).thenReturn(0L);
when(messageFrame.getRemainingGas()).thenReturn(0L);
when(transaction.getGasLimit()).thenReturn(100000L);
// Act
long refund = gasCalculator.calculateGasRefund(transaction, messageFrame, 0L);
// Assert
assertThat(refund).isEqualTo(0L);
}
}