Skip to content

Commit

Permalink
Merge branch 'main' of github.com:hyperledger/besu into permissioning…
Browse files Browse the repository at this point in the history
…-bft-test-watcher-junit5
  • Loading branch information
macfarla committed Jan 7, 2024
2 parents 6e5f4df + 658d153 commit d75529a
Show file tree
Hide file tree
Showing 11 changed files with 120 additions and 67 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 24.1.0-SNAPSHOT

### Breaking Changes
- New `EXECUTION_HALTED` error returned if there is an error executing or simulating a transaction, with the reason for execution being halted. Replaces the generic `INTERNAL_ERROR` return code in certain cases which some applications may be checking for [#6343](https://github.com/hyperledger/besu/pull/6343)

### Deprecations
- Forest pruning (`pruning-enabled` options) is deprecated and will be removed soon. To save disk space consider switching to Bonsai data storage format [#6230](https://github.com/hyperledger/besu/pull/6230)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonCallParameter;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
Expand Down Expand Up @@ -161,8 +162,12 @@ public void shouldNotIgnoreSenderBalanceAccountWhenStrictModeDisabledAndThrowErr
true,
null);
final JsonRpcRequestContext request = requestWithParams(callParameter);
final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(null, RpcErrorType.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE);

final RpcErrorType rpcErrorType = RpcErrorType.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE;
final JsonRpcError rpcError = new JsonRpcError(rpcErrorType);
rpcError.setReason(
"transaction up-front cost 0x1cc31b3333167018 exceeds transaction sender account balance 0x140");
final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError);

final JsonRpcResponse response = method.response(request);
assertThat(response).usingRecursiveComparison().isEqualTo(expectedResponse);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ public static RpcErrorType convertTransactionInvalidReason(
return RpcErrorType.PLUGIN_TX_VALIDATOR;
case INVALID_BLOBS:
return RpcErrorType.INVALID_BLOBS;
case EXECUTION_HALTED:
return RpcErrorType.EXECUTION_HALTED;
default:
return RpcErrorType.INTERNAL_ERROR;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,14 @@ protected JsonRpcErrorResponse errorResponse(
final ValidationResult<TransactionInvalidReason> validationResult =
result.getValidationResult();
if (validationResult != null && !validationResult.isValid()) {
if (validationResult.getErrorMessage().length() > 0) {
final RpcErrorType rpcErrorType =
JsonRpcErrorConverter.convertTransactionInvalidReason(
validationResult.getInvalidReason());
final JsonRpcError rpcError = new JsonRpcError(rpcErrorType);
rpcError.setReason(validationResult.getErrorMessage());
return errorResponse(request, rpcError);
}
return errorResponse(
request,
JsonRpcErrorConverter.convertTransactionInvalidReason(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ public String getData() {
return data;
}

public void setReason(final String reason) {
this.reason = reason;
}

@Override
public boolean equals(final Object o) {
if (this == o) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public enum RpcErrorType {
-32000, "An invalid transaction with a lower nonce exists"),
TOTAL_BLOB_GAS_TOO_HIGH(-32000, "Total blob gas too high"),
PLUGIN_TX_VALIDATOR(-32000, "Plugin has marked the transaction as invalid"),
EXECUTION_HALTED(-32000, "Transaction processing could not be completed due to an exception"),

// Execution engine failures
UNKNOWN_PAYLOAD(-32001, "Payload does not exist / is not available"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,13 @@ public void shouldReturnErrorWhenLegacyTransactionProcessorReturnsTxInvalidReaso
final JsonRpcRequestContext request =
ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO));
mockTransientProcessorResultTxInvalidReason(
TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE);
TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE,
"transaction up-front cost 10 exceeds transaction sender account balance 5");

final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(null, RpcErrorType.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE);
final RpcErrorType rpcErrorType = RpcErrorType.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE;
final JsonRpcError rpcError = new JsonRpcError(rpcErrorType);
rpcError.setReason("transaction up-front cost 10 exceeds transaction sender account balance 5");
final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError);

Assertions.assertThat(method.response(request))
.usingRecursiveComparison()
Expand All @@ -223,10 +226,13 @@ public void shouldReturnErrorWhenLegacyTransactionProcessorReturnsTxInvalidReaso
public void shouldReturnErrorWhenEip1559TransactionProcessorReturnsTxInvalidReason() {
final JsonRpcRequestContext request = ethEstimateGasRequest(eip1559TransactionCallParameter());
mockTransientProcessorResultTxInvalidReason(
TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE);
TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE,
"transaction up-front cost 10 exceeds transaction sender account balance 5");

final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(null, RpcErrorType.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE);
final RpcErrorType rpcErrorType = RpcErrorType.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE;
final JsonRpcError rpcError = new JsonRpcError(rpcErrorType);
rpcError.setReason("transaction up-front cost 10 exceeds transaction sender account balance 5");
final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError);

Assertions.assertThat(method.response(request))
.usingRecursiveComparison()
Expand All @@ -243,9 +249,9 @@ public void shouldReturnErrorWhenWorldStateIsNotAvailable() {
final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(null, RpcErrorType.WORLD_STATE_UNAVAILABLE);

Assertions.assertThat(method.response(request))
.usingRecursiveComparison()
.isEqualTo(expectedResponse);
JsonRpcResponse theResponse = method.response(request);

Assertions.assertThat(theResponse).usingRecursiveComparison().isEqualTo(expectedResponse);
}

@Test
Expand Down Expand Up @@ -364,10 +370,32 @@ public void shouldNotIgnoreSenderBalanceAccountWhenStrictModeDisabled() {
eq(1L));
}

private void mockTransientProcessorResultTxInvalidReason(final TransactionInvalidReason reason) {
@Test
public void shouldIncludeHaltReasonWhenExecutionHalts() {
final JsonRpcRequestContext request =
ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO));
mockTransientProcessorResultTxInvalidReason(
TransactionInvalidReason.EXECUTION_HALTED, "INVALID_OPERATION");

final RpcErrorType rpcErrorType = RpcErrorType.EXECUTION_HALTED;
final JsonRpcError rpcError = new JsonRpcError(rpcErrorType);
rpcError.setReason("INVALID_OPERATION");
final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError);

Assertions.assertThat(method.response(request))
.usingRecursiveComparison()
.isEqualTo(expectedResponse);
}

private void mockTransientProcessorResultTxInvalidReason(
final TransactionInvalidReason reason, final String validationFailedErrorMessage) {
final TransactionSimulatorResult mockTxSimResult =
getMockTransactionSimulatorResult(false, 0, Wei.ZERO, Optional.empty());
when(mockTxSimResult.getValidationResult()).thenReturn(ValidationResult.invalid(reason));
when(mockTxSimResult.getValidationResult())
.thenReturn(
validationFailedErrorMessage == null
? ValidationResult.invalid(reason)
: ValidationResult.invalid(reason, validationFailedErrorMessage));
}

private void mockTransientProcessorTxReverted(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
"jsonrpc": "2.0",
"id": 3,
"error": {
"code": -32603,
"message": "Internal error"
"code": -32000,
"message": "Transaction processing could not be completed due to an exception: INVALID_OPERATION"
}
},
"statusCode": 200
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,13 @@ public TransactionProcessingResult processTransaction(

if (initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) {
worldUpdater.commit();
} else {
if (initialFrame.getExceptionalHaltReason().isPresent()) {
validationResult =
ValidationResult.invalid(
TransactionInvalidReason.EXECUTION_HALTED,
initialFrame.getExceptionalHaltReason().get().toString());
}
}

if (LOG.isTraceEnabled()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public enum TransactionInvalidReason {
TX_POOL_DISABLED,
INVALID_BLOBS,
PLUGIN_TX_VALIDATOR,
EXECUTION_HALTED,
// Private Transaction Invalid Reasons
PRIVATE_TRANSACTION_INVALID,
PRIVATE_TRANSACTION_FAILED,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,59 +341,55 @@ private void clearStorage(
final Optional<BonsaiWorldStateKeyValueStorage.BonsaiUpdater> maybeStateUpdater,
final BonsaiWorldStateUpdateAccumulator worldStateUpdater) {

maybeStateUpdater.ifPresent(
bonsaiUpdater -> {
for (final Address address : worldStateUpdater.getStorageToClear()) {
// because we are clearing persisted values we need the account root as persisted
final BonsaiAccount oldAccount =
worldStateStorage
.getAccount(address.addressHash())
.map(
bytes -> BonsaiAccount.fromRLP(BonsaiWorldState.this, address, bytes, true))
.orElse(null);
if (oldAccount == null) {
// This is when an account is both created and deleted within the scope of the same
// block. A not-uncommon DeFi bot pattern.
continue;
}
final Hash addressHash = address.addressHash();
final MerkleTrie<Bytes, Bytes> storageTrie =
createTrie(
(location, key) -> getStorageTrieNode(addressHash, location, key),
oldAccount.getStorageRoot());
try {

final StorageConsumingMap<StorageSlotKey, BonsaiValue<UInt256>> storageToDelete =
worldStateUpdater.getStorageToUpdate().get(address);
Map<Bytes32, Bytes> entriesToDelete = storageTrie.entriesFrom(Bytes32.ZERO, 256);
while (!entriesToDelete.isEmpty()) {
entriesToDelete.forEach(
(k, v) -> {
final StorageSlotKey storageSlotKey =
new StorageSlotKey(Hash.wrap(k), Optional.empty());
final UInt256 slotValue =
UInt256.fromBytes(Bytes32.leftPad(RLP.decodeValue(v)));
bonsaiUpdater.removeStorageValueBySlotHash(
address.addressHash(), storageSlotKey.getSlotHash());
storageToDelete
.computeIfAbsent(
storageSlotKey, key -> new BonsaiValue<>(slotValue, null, true))
.setPrior(slotValue);
});
entriesToDelete.keySet().forEach(storageTrie::remove);
if (entriesToDelete.size() == 256) {
entriesToDelete = storageTrie.entriesFrom(Bytes32.ZERO, 256);
} else {
break;
}
}
} catch (MerkleTrieException e) {
// need to throw to trigger the heal
throw new MerkleTrieException(
e.getMessage(), Optional.of(Address.wrap(address)), e.getHash(), e.getLocation());
}
for (final Address address : worldStateUpdater.getStorageToClear()) {
// because we are clearing persisted values we need the account root as persisted
final BonsaiAccount oldAccount =
worldStateStorage
.getAccount(address.addressHash())
.map(bytes -> BonsaiAccount.fromRLP(BonsaiWorldState.this, address, bytes, true))
.orElse(null);
if (oldAccount == null) {
// This is when an account is both created and deleted within the scope of the same
// block. A not-uncommon DeFi bot pattern.
continue;
}
final Hash addressHash = address.addressHash();
final MerkleTrie<Bytes, Bytes> storageTrie =
createTrie(
(location, key) -> getStorageTrieNode(addressHash, location, key),
oldAccount.getStorageRoot());
try {
final StorageConsumingMap<StorageSlotKey, BonsaiValue<UInt256>> storageToDelete =
worldStateUpdater.getStorageToUpdate().get(address);
Map<Bytes32, Bytes> entriesToDelete = storageTrie.entriesFrom(Bytes32.ZERO, 256);
while (!entriesToDelete.isEmpty()) {
entriesToDelete.forEach(
(k, v) -> {
final StorageSlotKey storageSlotKey =
new StorageSlotKey(Hash.wrap(k), Optional.empty());
final UInt256 slotValue = UInt256.fromBytes(Bytes32.leftPad(RLP.decodeValue(v)));
maybeStateUpdater.ifPresent(
bonsaiUpdater ->
bonsaiUpdater.removeStorageValueBySlotHash(
address.addressHash(), storageSlotKey.getSlotHash()));
storageToDelete
.computeIfAbsent(
storageSlotKey, key -> new BonsaiValue<>(slotValue, null, true))
.setPrior(slotValue);
});
entriesToDelete.keySet().forEach(storageTrie::remove);
if (entriesToDelete.size() == 256) {
entriesToDelete = storageTrie.entriesFrom(Bytes32.ZERO, 256);
} else {
break;
}
});
}
} catch (MerkleTrieException e) {
// need to throw to trigger the heal
throw new MerkleTrieException(
e.getMessage(), Optional.of(Address.wrap(address)), e.getHash(), e.getLocation());
}
}
}

@Override
Expand Down

0 comments on commit d75529a

Please sign in to comment.