Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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 @@ -34,6 +34,7 @@
- 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.
- 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)
- 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.

### Additions and Improvements
- Add IPv6 dual-stack support for DiscV5 peer discovery (enabled via `--Xv5-discovery-enabled`): new `--p2p-host-ipv6`, `--p2p-interface-ipv6`, and `--p2p-port-ipv6` CLI options enable a second UDP discovery socket; `--p2p-ipv6-outbound-enabled` controls whether IPv6 is preferred for outbound connections when a peer advertises both address families [#9763](https://github.com/hyperledger/besu/pull/9763); RLPx now also binds a second TCP socket on the IPv6 interface so IPv6-only peers can establish connections [#9873](https://github.com/hyperledger/besu/pull/9873)
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, "Block gas limit exceeded by the block's transactions"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
BLOCK_GAS_LIMIT_EXCEEDED(-38015, "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