Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
- Fix QBFT `RLPException` when decoding proposals from pre-26.1.0 nodes that do not include the `blockAccessList` field [#9977](https://github.com/hyperledger/besu/pull/9977)
- Fix eth_simulateV1 discrepancy [9960] (https://github.com/besu-eth/besu/issues/9960) eth_simulateV1 now accepts calls where both input and data
are provided with different values, using input as per the execution-apis spec instead of returning an error.
- Fix eth_simulateV1 returning wrong error code when transaction gas exceeds block gas limit: now correctly returns -38015 (BLOCK_GAS_LIMIT_EXCEEDED) instead of -38014 or succeeding [#10073](https://github.com/besu-eth/besu/pull/10073)
Comment thread
macfarla marked this conversation as resolved.
- Wait for peers before starting chain download. Prevents an OutOfMemory (OOM) error when the node has zero peers [#9979](https://github.com/hyperledger/besu/pull/9979)

### Additions and Improvements
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,14 @@ protected BlockStateCallSimulationResult processTransactions(
}
}

if (callParameter.getGas().isPresent()
&& callParameter.getGas().getAsLong()
> blockStateCallSimulationResult.getRemainingGas()) {
throw new BlockStateCallException(
BlockStateCallError.BLOCK_GAS_LIMIT_EXCEEDED.getMessage(),
BlockStateCallError.BLOCK_GAS_LIMIT_EXCEEDED);
}

long gasLimit =
transactionSimulator.calculateSimulationGasCap(
blockHeader,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public enum BlockStateCallError {
DUPLICATED_PRECOMPILE_TARGET(-38023, "Duplicated move precompile target"),
/** The nonce is invalid. */
INVALID_NONCES(-32602, "Invalid nonces"),
/** Block gas limit exceeded by the block's transactions. */
BLOCK_GAS_LIMIT_EXCEEDED(-38015, "Transaction gas exceeds block gas limit"),
/** Upfront cost exceeds balance. */
UPFRONT_COST_EXCEEDS_BALANCE(-38014, "Upfront cost exceeds balance"),
/** Gas price too low. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.StateOverride;
import org.hyperledger.besu.datatypes.StateOverrideMap;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.datatypes.parameters.UnsignedLongParameter;
import org.hyperledger.besu.ethereum.GasLimitCalculator;
Expand All @@ -38,12 +39,16 @@
import org.hyperledger.besu.ethereum.core.Difficulty;
import org.hyperledger.besu.ethereum.core.MiningConfiguration;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.TransactionReceipt;
import org.hyperledger.besu.ethereum.mainnet.AbstractBlockProcessor;
import org.hyperledger.besu.ethereum.mainnet.MiningBeneficiaryCalculator;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import org.hyperledger.besu.ethereum.mainnet.blockhash.PreExecutionProcessor;
import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket;
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
import org.hyperledger.besu.ethereum.transaction.exceptions.BlockStateCallError;
import org.hyperledger.besu.ethereum.transaction.exceptions.BlockStateCallException;
import org.hyperledger.besu.ethereum.trie.pathbased.common.provider.WorldStateQueryParams;
Expand All @@ -57,6 +62,7 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.Set;

import org.apache.tuweni.bytes.Bytes;
Expand Down Expand Up @@ -145,6 +151,7 @@ public void shouldStopWhenTransactionSimulationIsInvalid() {
when(mutableWorldState.updater()).thenReturn(updater);

CallParameter callParameter = mock(CallParameter.class);
when(callParameter.getGas()).thenReturn(OptionalLong.empty());
BlockStateCall blockStateCall = new BlockStateCall(List.of(callParameter), null, null);

TransactionSimulatorResult transactionSimulatorResult = mock(TransactionSimulatorResult.class);
Expand Down Expand Up @@ -179,6 +186,7 @@ public void shouldStopWhenTransactionSimulationIsEmpty() {
when(mutableWorldState.updater()).thenReturn(updater);

CallParameter callParameter = mock(CallParameter.class);
when(callParameter.getGas()).thenReturn(OptionalLong.empty());
BlockStateCall blockStateCall = new BlockStateCall(List.of(callParameter), null, null);

when(transactionSimulator.processWithWorldUpdater(
Expand Down Expand Up @@ -307,6 +315,120 @@ public void shouldAllowDuplicatePrecompileTargetAddresses() {
assertThat(validationResult).isEmpty();
}

@Test
public void shouldThrowBlockGasLimitExceededWhenTxGasExceedsBlockLimitWithValidationDisabled() {
when(mutableWorldState.updater()).thenReturn(updater);

// gasLimitCalculator.nextGasLimit returns 1L (from setUp), so block gas limit = 1
CallParameter callParameter = mock(CallParameter.class);
when(callParameter.getGas()).thenReturn(OptionalLong.of(1_000_000L));
BlockStateCall blockStateCall = new BlockStateCall(List.of(callParameter), null, null);

BlockSimulationParameter parameter =
new BlockSimulationParameter.BlockSimulationParameterBuilder()
.blockStateCalls(List.of(blockStateCall))
.validation(false)
.build();

BlockStateCallException exception =
assertThrows(
BlockStateCallException.class,
() -> blockSimulator.process(blockHeader, parameter, mutableWorldState));

assertThat(exception.getError()).isEqualTo(BlockStateCallError.BLOCK_GAS_LIMIT_EXCEEDED);
assertThat(exception.getError().getCode()).isEqualTo(-38015);
}

@Test
public void shouldThrowBlockGasLimitExceededWhenTxGasExceedsBlockLimitWithValidationEnabled() {
when(mutableWorldState.updater()).thenReturn(updater);

// gasLimitCalculator.nextGasLimit returns 1L (from setUp), so block gas limit = 1
CallParameter callParameter = mock(CallParameter.class);
when(callParameter.getGas()).thenReturn(OptionalLong.of(1_000_000L));
BlockStateCall blockStateCall = new BlockStateCall(List.of(callParameter), null, null);

BlockSimulationParameter parameter =
new BlockSimulationParameter.BlockSimulationParameterBuilder()
.blockStateCalls(List.of(blockStateCall))
.validation(true)
.build();

BlockStateCallException exception =
assertThrows(
BlockStateCallException.class,
() -> blockSimulator.process(blockHeader, parameter, mutableWorldState));

assertThat(exception.getError()).isEqualTo(BlockStateCallError.BLOCK_GAS_LIMIT_EXCEEDED);
assertThat(exception.getError().getCode()).isEqualTo(-38015);
}

@Test
public void
shouldThrowBlockGasLimitExceededWhenSecondTxGasExceedsRemainingAfterFirstTxConsumed() {
// Block gas limit = 30,000. First tx consumes 21,000 (leaving 9,000 remaining).
// Second tx explicitly requests 10,000 gas, which exceeds the 9,000 remaining.
GasLimitCalculator gasLimitCalculator = mock(GasLimitCalculator.class);
when(protocolSpec.getGasLimitCalculator()).thenReturn(gasLimitCalculator);
when(gasLimitCalculator.nextGasLimit(anyLong(), anyLong(), anyLong())).thenReturn(30_000L);
when(gasLimitCalculator.computeExcessBlobGas(anyLong(), anyLong(), anyLong())).thenReturn(0L);

WorldUpdater transactionUpdater = mock(WorldUpdater.class);
when(mutableWorldState.updater()).thenReturn(updater);
when(updater.updater()).thenReturn(transactionUpdater);

CallParameter firstCallParam = mock(CallParameter.class);
when(firstCallParam.getGas()).thenReturn(OptionalLong.empty());
when(firstCallParam.getGasPrice()).thenReturn(Optional.empty());
when(firstCallParam.getMaxFeePerGas()).thenReturn(Optional.empty());
when(firstCallParam.getMaxPriorityFeePerGas()).thenReturn(Optional.empty());

CallParameter secondCallParam = mock(CallParameter.class);
when(secondCallParam.getGas()).thenReturn(OptionalLong.of(10_000L));

BlockStateCall blockStateCall =
new BlockStateCall(List.of(firstCallParam, secondCallParam), null, null);

Transaction tx = mock(Transaction.class);
when(tx.getType()).thenReturn(TransactionType.FRONTIER);

TransactionProcessingResult processingResult = mock(TransactionProcessingResult.class);
when(processingResult.getPartialBlockAccessView()).thenReturn(Optional.empty());

TransactionSimulatorResult firstTxResult = mock(TransactionSimulatorResult.class);
when(firstTxResult.isInvalid()).thenReturn(false);
when(firstTxResult.getGasEstimate()).thenReturn(21_000L);
when(firstTxResult.transaction()).thenReturn(tx);
when(firstTxResult.result()).thenReturn(processingResult);

when(transactionSimulator.processWithWorldUpdater(
any(), any(), any(), any(), any(), any(), any(), anyLong(), any(), any(), any(), any(),
any()))
.thenReturn(Optional.of(firstTxResult));

AbstractBlockProcessor.TransactionReceiptFactory receiptFactory =
mock(AbstractBlockProcessor.TransactionReceiptFactory.class);
when(protocolSpec.getTransactionReceiptFactory()).thenReturn(receiptFactory);
TransactionReceipt receipt = mock(TransactionReceipt.class);
when(receipt.getLogsList()).thenReturn(List.of());
when(receiptFactory.create(any(TransactionType.class), any(), any(), anyLong()))
.thenReturn(receipt);

BlockSimulationParameter parameter =
new BlockSimulationParameter.BlockSimulationParameterBuilder()
.blockStateCalls(List.of(blockStateCall))
.validation(false)
.build();

BlockStateCallException exception =
assertThrows(
BlockStateCallException.class,
() -> blockSimulator.process(blockHeader, parameter, mutableWorldState));

assertThat(exception.getError()).isEqualTo(BlockStateCallError.BLOCK_GAS_LIMIT_EXCEEDED);
assertThat(exception.getError().getCode()).isEqualTo(-38015);
}

private BlockSimulationParameter createSimulationParameter(final BlockStateCall blockStateCall) {
return new BlockSimulationParameter.BlockSimulationParameterBuilder()
.blockStateCalls(List.of(blockStateCall))
Expand Down
Loading