Fix memory expansion bounds checking (#1322)

When a CALL series operation has an erroneous input offset (such as
starting at a ETH address instead of a real offset) we threw an
ArithmeticException.

* Restore the old memory bounds checking on memory expansion
* Treat these formerly uncaught exceptions as invalid transactions and
  report errors with a stack trace and custom halt reason.

Signed-off-by: Danno Ferrin <danno.ferrin@gmail.com>
pull/1328/head
Danno Ferrin 4 years ago committed by GitHub
parent 3a39fbb707
commit 3f64a14432
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 362
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java
  2. 3
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidator.java
  3. 245
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionProcessor.java
  4. 6
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/AbstractCallOperation.java
  5. 2
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/EVM.java
  6. 18
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/Memory.java
  7. 2
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/MessageFrame.java

@ -27,6 +27,7 @@ import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.core.WorldUpdater; import org.hyperledger.besu.ethereum.core.WorldUpdater;
import org.hyperledger.besu.ethereum.core.fees.CoinbaseFeePriceCalculator; import org.hyperledger.besu.ethereum.core.fees.CoinbaseFeePriceCalculator;
import org.hyperledger.besu.ethereum.core.fees.TransactionPriceCalculator; import org.hyperledger.besu.ethereum.core.fees.TransactionPriceCalculator;
import org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInvalidReason;
import org.hyperledger.besu.ethereum.vm.BlockHashLookup; import org.hyperledger.besu.ethereum.vm.BlockHashLookup;
import org.hyperledger.besu.ethereum.vm.Code; import org.hyperledger.besu.ethereum.vm.Code;
import org.hyperledger.besu.ethereum.vm.GasCalculator; import org.hyperledger.besu.ethereum.vm.GasCalculator;
@ -208,193 +209,204 @@ public class MainnetTransactionProcessor implements TransactionProcessor {
final BlockHashLookup blockHashLookup, final BlockHashLookup blockHashLookup,
final Boolean isPersistingPrivateState, final Boolean isPersistingPrivateState,
final TransactionValidationParams transactionValidationParams) { final TransactionValidationParams transactionValidationParams) {
LOG.trace("Starting execution of {}", transaction); try {
LOG.trace("Starting execution of {}", transaction);
ValidationResult<TransactionValidator.TransactionInvalidReason> validationResult =
transactionValidator.validate(transaction, blockHeader.getBaseFee()); ValidationResult<TransactionValidator.TransactionInvalidReason> validationResult =
// Make sure the transaction is intrinsically valid before trying to transactionValidator.validate(transaction, blockHeader.getBaseFee());
// compare against a sender account (because the transaction may not // Make sure the transaction is intrinsically valid before trying to
// be signed correctly to extract the sender). // compare against a sender account (because the transaction may not
if (!validationResult.isValid()) { // be signed correctly to extract the sender).
LOG.warn("Invalid transaction: {}", validationResult.getErrorMessage()); if (!validationResult.isValid()) {
return Result.invalid(validationResult); LOG.warn("Invalid transaction: {}", validationResult.getErrorMessage());
} return Result.invalid(validationResult);
}
final Address senderAddress = transaction.getSender(); final Address senderAddress = transaction.getSender();
final DefaultEvmAccount sender = worldState.getOrCreate(senderAddress); final DefaultEvmAccount sender = worldState.getOrCreate(senderAddress);
validationResult = validationResult =
transactionValidator.validateForSender(transaction, sender, transactionValidationParams); transactionValidator.validateForSender(transaction, sender, transactionValidationParams);
if (!validationResult.isValid()) { if (!validationResult.isValid()) {
LOG.debug("Invalid transaction: {}", validationResult.getErrorMessage()); LOG.debug("Invalid transaction: {}", validationResult.getErrorMessage());
return Result.invalid(validationResult); return Result.invalid(validationResult);
} }
final MutableAccount senderMutableAccount = sender.getMutable(); final MutableAccount senderMutableAccount = sender.getMutable();
final long previousNonce = senderMutableAccount.incrementNonce(); final long previousNonce = senderMutableAccount.incrementNonce();
final Wei transactionGasPrice = final Wei transactionGasPrice =
transactionPriceCalculator.price(transaction, blockHeader.getBaseFee()); transactionPriceCalculator.price(transaction, blockHeader.getBaseFee());
LOG.trace( LOG.trace(
"Incremented sender {} nonce ({} -> {})", senderAddress, previousNonce, sender.getNonce()); "Incremented sender {} nonce ({} -> {})",
senderAddress,
final Wei upfrontGasCost = transaction.getUpfrontGasCost(transactionGasPrice); previousNonce,
final Wei previousBalance = senderMutableAccount.decrementBalance(upfrontGasCost); sender.getNonce());
LOG.trace(
"Deducted sender {} upfront gas cost {} ({} -> {})",
senderAddress,
upfrontGasCost,
previousBalance,
sender.getBalance());
final Gas intrinsicGas = gasCalculator.transactionIntrinsicGasCost(transaction);
final Gas gasAvailable = Gas.of(transaction.getGasLimit()).minus(intrinsicGas);
LOG.trace(
"Gas available for execution {} = {} - {} (limit - intrinsic)",
gasAvailable,
transaction.getGasLimit(),
intrinsicGas);
final WorldUpdater worldUpdater = worldState.updater();
final MessageFrame initialFrame;
final Deque<MessageFrame> messageFrameStack = new ArrayDeque<>();
final ReturnStack returnStack = new ReturnStack();
if (transaction.isContractCreation()) {
final Address contractAddress =
Address.contractAddress(senderAddress, sender.getNonce() - 1L);
initialFrame =
MessageFrame.builder()
.type(MessageFrame.Type.CONTRACT_CREATION)
.messageFrameStack(messageFrameStack)
.returnStack(returnStack)
.blockchain(blockchain)
.worldState(worldUpdater.updater())
.initialGas(gasAvailable)
.address(contractAddress)
.originator(senderAddress)
.contract(contractAddress)
.contractAccountVersion(createContractAccountVersion)
.gasPrice(transactionGasPrice)
.inputData(Bytes.EMPTY)
.sender(senderAddress)
.value(transaction.getValue())
.apparentValue(transaction.getValue())
.code(new Code(transaction.getPayload()))
.blockHeader(blockHeader)
.depth(0)
.completer(c -> {})
.miningBeneficiary(miningBeneficiary)
.blockHashLookup(blockHashLookup)
.isPersistingPrivateState(isPersistingPrivateState)
.maxStackSize(maxStackSize)
.transactionHash(transaction.getHash())
.build();
} else {
final Address to = transaction.getTo().get();
final Account contract = worldState.get(to);
initialFrame =
MessageFrame.builder()
.type(MessageFrame.Type.MESSAGE_CALL)
.messageFrameStack(messageFrameStack)
.returnStack(returnStack)
.blockchain(blockchain)
.worldState(worldUpdater.updater())
.initialGas(gasAvailable)
.address(to)
.originator(senderAddress)
.contract(to)
.contractAccountVersion(
contract != null ? contract.getVersion() : Account.DEFAULT_VERSION)
.gasPrice(transactionGasPrice)
.inputData(transaction.getPayload())
.sender(senderAddress)
.value(transaction.getValue())
.apparentValue(transaction.getValue())
.code(new Code(contract != null ? contract.getCode() : Bytes.EMPTY))
.blockHeader(blockHeader)
.depth(0)
.completer(c -> {})
.miningBeneficiary(miningBeneficiary)
.blockHashLookup(blockHashLookup)
.maxStackSize(maxStackSize)
.isPersistingPrivateState(isPersistingPrivateState)
.transactionHash(transaction.getHash())
.build();
}
messageFrameStack.addFirst(initialFrame); final Wei upfrontGasCost = transaction.getUpfrontGasCost(transactionGasPrice);
final Wei previousBalance = senderMutableAccount.decrementBalance(upfrontGasCost);
LOG.trace(
"Deducted sender {} upfront gas cost {} ({} -> {})",
senderAddress,
upfrontGasCost,
previousBalance,
sender.getBalance());
final Gas intrinsicGas = gasCalculator.transactionIntrinsicGasCost(transaction);
final Gas gasAvailable = Gas.of(transaction.getGasLimit()).minus(intrinsicGas);
LOG.trace(
"Gas available for execution {} = {} - {} (limit - intrinsic)",
gasAvailable,
transaction.getGasLimit(),
intrinsicGas);
final WorldUpdater worldUpdater = worldState.updater();
final MessageFrame initialFrame;
final Deque<MessageFrame> messageFrameStack = new ArrayDeque<>();
final ReturnStack returnStack = new ReturnStack();
if (transaction.isContractCreation()) {
final Address contractAddress =
Address.contractAddress(senderAddress, sender.getNonce() - 1L);
initialFrame =
MessageFrame.builder()
.type(MessageFrame.Type.CONTRACT_CREATION)
.messageFrameStack(messageFrameStack)
.returnStack(returnStack)
.blockchain(blockchain)
.worldState(worldUpdater.updater())
.initialGas(gasAvailable)
.address(contractAddress)
.originator(senderAddress)
.contract(contractAddress)
.contractAccountVersion(createContractAccountVersion)
.gasPrice(transactionGasPrice)
.inputData(Bytes.EMPTY)
.sender(senderAddress)
.value(transaction.getValue())
.apparentValue(transaction.getValue())
.code(new Code(transaction.getPayload()))
.blockHeader(blockHeader)
.depth(0)
.completer(c -> {})
.miningBeneficiary(miningBeneficiary)
.blockHashLookup(blockHashLookup)
.isPersistingPrivateState(isPersistingPrivateState)
.maxStackSize(maxStackSize)
.transactionHash(transaction.getHash())
.build();
} else {
final Address to = transaction.getTo().get();
final Account contract = worldState.get(to);
initialFrame =
MessageFrame.builder()
.type(MessageFrame.Type.MESSAGE_CALL)
.messageFrameStack(messageFrameStack)
.returnStack(returnStack)
.blockchain(blockchain)
.worldState(worldUpdater.updater())
.initialGas(gasAvailable)
.address(to)
.originator(senderAddress)
.contract(to)
.contractAccountVersion(
contract != null ? contract.getVersion() : Account.DEFAULT_VERSION)
.gasPrice(transactionGasPrice)
.inputData(transaction.getPayload())
.sender(senderAddress)
.value(transaction.getValue())
.apparentValue(transaction.getValue())
.code(new Code(contract != null ? contract.getCode() : Bytes.EMPTY))
.blockHeader(blockHeader)
.depth(0)
.completer(c -> {})
.miningBeneficiary(miningBeneficiary)
.blockHashLookup(blockHashLookup)
.maxStackSize(maxStackSize)
.isPersistingPrivateState(isPersistingPrivateState)
.transactionHash(transaction.getHash())
.build();
}
while (!messageFrameStack.isEmpty()) { messageFrameStack.addFirst(initialFrame);
process(messageFrameStack.peekFirst(), operationTracer);
}
if (initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { while (!messageFrameStack.isEmpty()) {
worldUpdater.commit(); process(messageFrameStack.peekFirst(), operationTracer);
} }
if (LOG.isTraceEnabled()) { if (initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) {
LOG.trace( worldUpdater.commit();
"Gas used by transaction: {}, by message call/contract creation: {}", }
() -> Gas.of(transaction.getGasLimit()).minus(initialFrame.getRemainingGas()),
() -> gasAvailable.minus(initialFrame.getRemainingGas()));
}
// Refund the sender by what we should and pay the miner fee (note that we're doing them one if (LOG.isTraceEnabled()) {
// after the other so that if it is the same account somehow, we end up with the right result) LOG.trace(
final Gas selfDestructRefund = "Gas used by transaction: {}, by message call/contract creation: {}",
gasCalculator.getSelfDestructRefundAmount().times(initialFrame.getSelfDestructs().size()); () -> Gas.of(transaction.getGasLimit()).minus(initialFrame.getRemainingGas()),
final Gas refundGas = initialFrame.getGasRefund().plus(selfDestructRefund); () -> gasAvailable.minus(initialFrame.getRemainingGas()));
final Gas refunded = refunded(transaction, initialFrame.getRemainingGas(), refundGas);
final Wei refundedWei = refunded.priceFor(transactionGasPrice);
senderMutableAccount.incrementBalance(refundedWei);
final Gas gasUsedByTransaction =
Gas.of(transaction.getGasLimit()).minus(initialFrame.getRemainingGas());
final MutableAccount coinbase = worldState.getOrCreate(miningBeneficiary).getMutable();
final Gas coinbaseFee = Gas.of(transaction.getGasLimit()).minus(refunded);
if (blockHeader.getBaseFee().isPresent() && transaction.isEIP1559Transaction()) {
final Wei baseFee = Wei.of(blockHeader.getBaseFee().get());
if (transactionGasPrice.compareTo(baseFee) < 0) {
return Result.failed(
gasUsedByTransaction.toLong(),
refunded.toLong(),
ValidationResult.invalid(
TransactionValidator.TransactionInvalidReason.TRANSACTION_PRICE_TOO_LOW,
"transaction price must be greater than base fee"),
Optional.empty());
} }
}
final CoinbaseFeePriceCalculator coinbaseCreditService =
transaction.isFrontierTransaction()
? CoinbaseFeePriceCalculator.frontier()
: coinbaseFeePriceCalculator;
final Wei coinbaseWeiDelta =
coinbaseCreditService.price(coinbaseFee, transactionGasPrice, blockHeader.getBaseFee());
coinbase.incrementBalance(coinbaseWeiDelta); // 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 Gas selfDestructRefund =
gasCalculator.getSelfDestructRefundAmount().times(initialFrame.getSelfDestructs().size());
final Gas refundGas = initialFrame.getGasRefund().plus(selfDestructRefund);
final Gas refunded = refunded(transaction, initialFrame.getRemainingGas(), refundGas);
final Wei refundedWei = refunded.priceFor(transactionGasPrice);
senderMutableAccount.incrementBalance(refundedWei);
final Gas gasUsedByTransaction =
Gas.of(transaction.getGasLimit()).minus(initialFrame.getRemainingGas());
final MutableAccount coinbase = worldState.getOrCreate(miningBeneficiary).getMutable();
final Gas coinbaseFee = Gas.of(transaction.getGasLimit()).minus(refunded);
if (blockHeader.getBaseFee().isPresent() && transaction.isEIP1559Transaction()) {
final Wei baseFee = Wei.of(blockHeader.getBaseFee().get());
if (transactionGasPrice.compareTo(baseFee) < 0) {
return Result.failed(
gasUsedByTransaction.toLong(),
refunded.toLong(),
ValidationResult.invalid(
TransactionValidator.TransactionInvalidReason.TRANSACTION_PRICE_TOO_LOW,
"transaction price must be greater than base fee"),
Optional.empty());
}
}
final CoinbaseFeePriceCalculator coinbaseCreditService =
transaction.isFrontierTransaction()
? CoinbaseFeePriceCalculator.frontier()
: coinbaseFeePriceCalculator;
final Wei coinbaseWeiDelta =
coinbaseCreditService.price(coinbaseFee, transactionGasPrice, blockHeader.getBaseFee());
initialFrame.getSelfDestructs().forEach(worldState::deleteAccount); coinbase.incrementBalance(coinbaseWeiDelta);
if (clearEmptyAccounts) { initialFrame.getSelfDestructs().forEach(worldState::deleteAccount);
clearEmptyAccounts(worldState);
}
if (initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { if (clearEmptyAccounts) {
return Result.successful( clearEmptyAccounts(worldState);
initialFrame.getLogs(), }
gasUsedByTransaction.toLong(),
refunded.toLong(), if (initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) {
initialFrame.getOutputData(), return Result.successful(
validationResult); initialFrame.getLogs(),
} else { gasUsedByTransaction.toLong(),
return Result.failed( refunded.toLong(),
gasUsedByTransaction.toLong(), initialFrame.getOutputData(),
refunded.toLong(), validationResult);
validationResult, } else {
initialFrame.getRevertReason()); return Result.failed(
gasUsedByTransaction.toLong(),
refunded.toLong(),
validationResult,
initialFrame.getRevertReason());
}
} catch (final RuntimeException re) {
LOG.error("Critical Exception Processing Transaction", re);
return Result.invalid(
ValidationResult.invalid(
TransactionInvalidReason.INTERNAL_ERROR,
"Internal Error in Besu - " + re.toString()));
} }
} }

@ -87,6 +87,7 @@ public interface TransactionValidator {
GAS_PRICE_TOO_LOW, GAS_PRICE_TOO_LOW,
TX_FEECAP_EXCEEDED, TX_FEECAP_EXCEEDED,
PRIVATE_VALUE_NOT_ZERO, PRIVATE_VALUE_NOT_ZERO,
PRIVATE_UNIMPLEMENTED_TRANSACTION_TYPE; PRIVATE_UNIMPLEMENTED_TRANSACTION_TYPE,
INTERNAL_ERROR;
} }
} }

@ -29,6 +29,7 @@ import org.hyperledger.besu.ethereum.core.WorldUpdater;
import org.hyperledger.besu.ethereum.mainnet.AbstractMessageProcessor; import org.hyperledger.besu.ethereum.mainnet.AbstractMessageProcessor;
import org.hyperledger.besu.ethereum.mainnet.TransactionProcessor; import org.hyperledger.besu.ethereum.mainnet.TransactionProcessor;
import org.hyperledger.besu.ethereum.mainnet.TransactionValidator; import org.hyperledger.besu.ethereum.mainnet.TransactionValidator;
import org.hyperledger.besu.ethereum.mainnet.TransactionValidator.TransactionInvalidReason;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import org.hyperledger.besu.ethereum.vm.BlockHashLookup; import org.hyperledger.besu.ethereum.vm.BlockHashLookup;
import org.hyperledger.besu.ethereum.vm.Code; import org.hyperledger.besu.ethereum.vm.Code;
@ -213,127 +214,135 @@ public class PrivateTransactionProcessor {
final OperationTracer operationTracer, final OperationTracer operationTracer,
final BlockHashLookup blockHashLookup, final BlockHashLookup blockHashLookup,
final Bytes privacyGroupId) { final Bytes privacyGroupId) {
LOG.trace("Starting private execution of {}", transaction); try {
LOG.trace("Starting private execution of {}", transaction);
final Address senderAddress = transaction.getSender();
final DefaultEvmAccount maybePrivateSender = privateWorldState.getAccount(senderAddress); final Address senderAddress = transaction.getSender();
final MutableAccount sender = final DefaultEvmAccount maybePrivateSender = privateWorldState.getAccount(senderAddress);
maybePrivateSender != null final MutableAccount sender =
? maybePrivateSender.getMutable() maybePrivateSender != null
: privateWorldState.createAccount(senderAddress, 0, Wei.ZERO).getMutable(); ? maybePrivateSender.getMutable()
: privateWorldState.createAccount(senderAddress, 0, Wei.ZERO).getMutable();
final ValidationResult<TransactionValidator.TransactionInvalidReason> validationResult =
privateTransactionValidator.validate(transaction, sender.getNonce(), false); final ValidationResult<TransactionValidator.TransactionInvalidReason> validationResult =
if (!validationResult.isValid()) { privateTransactionValidator.validate(transaction, sender.getNonce(), false);
return Result.invalid(validationResult); if (!validationResult.isValid()) {
} return Result.invalid(validationResult);
}
final long previousNonce = sender.incrementNonce();
LOG.trace( final long previousNonce = sender.incrementNonce();
"Incremented private sender {} nonce ({} -> {})", LOG.trace(
senderAddress, "Incremented private sender {} nonce ({} -> {})",
previousNonce,
sender.getNonce());
final MessageFrame initialFrame;
final Deque<MessageFrame> messageFrameStack = new ArrayDeque<>();
final WorldUpdater mutablePrivateWorldStateUpdater =
new DefaultMutablePrivateWorldStateUpdater(publicWorldState, privateWorldState);
final ReturnStack returnStack = new ReturnStack();
if (transaction.isContractCreation()) {
final Address privateContractAddress =
Address.privateContractAddress(senderAddress, previousNonce, privacyGroupId);
LOG.debug(
"Calculated contract address {} from sender {} with nonce {} and privacy group {}",
privateContractAddress.toString(),
senderAddress, senderAddress,
previousNonce, previousNonce,
privacyGroupId.toString()); sender.getNonce());
initialFrame = final MessageFrame initialFrame;
MessageFrame.builder() final Deque<MessageFrame> messageFrameStack = new ArrayDeque<>();
.type(MessageFrame.Type.CONTRACT_CREATION)
.messageFrameStack(messageFrameStack) final WorldUpdater mutablePrivateWorldStateUpdater =
.returnStack(returnStack) new DefaultMutablePrivateWorldStateUpdater(publicWorldState, privateWorldState);
.blockchain(blockchain)
.worldState(mutablePrivateWorldStateUpdater) final ReturnStack returnStack = new ReturnStack();
.address(privateContractAddress)
.originator(senderAddress) if (transaction.isContractCreation()) {
.contract(privateContractAddress) final Address privateContractAddress =
.contractAccountVersion(createContractAccountVersion) Address.privateContractAddress(senderAddress, previousNonce, privacyGroupId);
.initialGas(Gas.MAX_VALUE)
.gasPrice(transaction.getGasPrice()) LOG.debug(
.inputData(Bytes.EMPTY) "Calculated contract address {} from sender {} with nonce {} and privacy group {}",
.sender(senderAddress) privateContractAddress.toString(),
.value(transaction.getValue()) senderAddress,
.apparentValue(transaction.getValue()) previousNonce,
.code(new Code(transaction.getPayload())) privacyGroupId.toString());
.blockHeader(blockHeader)
.depth(0) initialFrame =
.completer(c -> {}) MessageFrame.builder()
.miningBeneficiary(miningBeneficiary) .type(MessageFrame.Type.CONTRACT_CREATION)
.blockHashLookup(blockHashLookup) .messageFrameStack(messageFrameStack)
.maxStackSize(maxStackSize) .returnStack(returnStack)
.transactionHash(pmtHash) .blockchain(blockchain)
.build(); .worldState(mutablePrivateWorldStateUpdater)
.address(privateContractAddress)
} else { .originator(senderAddress)
final Address to = transaction.getTo().get(); .contract(privateContractAddress)
final Account contract = privateWorldState.get(to); .contractAccountVersion(createContractAccountVersion)
.initialGas(Gas.MAX_VALUE)
initialFrame = .gasPrice(transaction.getGasPrice())
MessageFrame.builder() .inputData(Bytes.EMPTY)
.type(MessageFrame.Type.MESSAGE_CALL) .sender(senderAddress)
.messageFrameStack(messageFrameStack) .value(transaction.getValue())
.returnStack(returnStack) .apparentValue(transaction.getValue())
.blockchain(blockchain) .code(new Code(transaction.getPayload()))
.worldState(mutablePrivateWorldStateUpdater) .blockHeader(blockHeader)
.address(to) .depth(0)
.originator(senderAddress) .completer(c -> {})
.contract(to) .miningBeneficiary(miningBeneficiary)
.contractAccountVersion( .blockHashLookup(blockHashLookup)
contract != null ? contract.getVersion() : Account.DEFAULT_VERSION) .maxStackSize(maxStackSize)
.initialGas(Gas.MAX_VALUE) .transactionHash(pmtHash)
.gasPrice(transaction.getGasPrice()) .build();
.inputData(transaction.getPayload())
.sender(senderAddress) } else {
.value(transaction.getValue()) final Address to = transaction.getTo().get();
.apparentValue(transaction.getValue()) final Account contract = privateWorldState.get(to);
.code(new Code(contract != null ? contract.getCode() : Bytes.EMPTY))
.blockHeader(blockHeader) initialFrame =
.depth(0) MessageFrame.builder()
.completer(c -> {}) .type(MessageFrame.Type.MESSAGE_CALL)
.miningBeneficiary(miningBeneficiary) .messageFrameStack(messageFrameStack)
.blockHashLookup(blockHashLookup) .returnStack(returnStack)
.maxStackSize(maxStackSize) .blockchain(blockchain)
.transactionHash(pmtHash) .worldState(mutablePrivateWorldStateUpdater)
.build(); .address(to)
} .originator(senderAddress)
.contract(to)
messageFrameStack.addFirst(initialFrame); .contractAccountVersion(
contract != null ? contract.getVersion() : Account.DEFAULT_VERSION)
while (!messageFrameStack.isEmpty()) { .initialGas(Gas.MAX_VALUE)
process(messageFrameStack.peekFirst(), operationTracer); .gasPrice(transaction.getGasPrice())
} .inputData(transaction.getPayload())
.sender(senderAddress)
if (initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { .value(transaction.getValue())
mutablePrivateWorldStateUpdater.commit(); .apparentValue(transaction.getValue())
} .code(new Code(contract != null ? contract.getCode() : Bytes.EMPTY))
.blockHeader(blockHeader)
if (initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { .depth(0)
return Result.successful( .completer(c -> {})
initialFrame.getLogs(), 0, 0, initialFrame.getOutputData(), ValidationResult.valid()); .miningBeneficiary(miningBeneficiary)
} else { .blockHashLookup(blockHashLookup)
return Result.failed( .maxStackSize(maxStackSize)
0, .transactionHash(pmtHash)
0, .build();
}
messageFrameStack.addFirst(initialFrame);
while (!messageFrameStack.isEmpty()) {
process(messageFrameStack.peekFirst(), operationTracer);
}
if (initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) {
mutablePrivateWorldStateUpdater.commit();
}
if (initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) {
return Result.successful(
initialFrame.getLogs(), 0, 0, initialFrame.getOutputData(), ValidationResult.valid());
} else {
return Result.failed(
0,
0,
ValidationResult.invalid(
TransactionValidator.TransactionInvalidReason.PRIVATE_TRANSACTION_FAILED),
initialFrame.getRevertReason());
}
} catch (final RuntimeException re) {
LOG.error("Critical Exception Processing Transaction", re);
return Result.invalid(
ValidationResult.invalid( ValidationResult.invalid(
TransactionValidator.TransactionInvalidReason.PRIVATE_TRANSACTION_FAILED), TransactionInvalidReason.INTERNAL_ERROR,
initialFrame.getRevertReason()); "Internal Error in Besu - " + re.toString()));
} }
} }

@ -171,8 +171,8 @@ public abstract class AbstractCallOperation extends AbstractOperation {
final Account account = frame.getWorldState().get(frame.getRecipientAddress()); final Account account = frame.getWorldState().get(frame.getRecipientAddress());
final Wei balance = account.getBalance(); final Wei balance = account.getBalance();
if (value(frame).compareTo(balance) > 0 || frame.getMessageStackDepth() >= 1024) { if (value(frame).compareTo(balance) > 0 || frame.getMessageStackDepth() >= 1024) {
frame.expandMemory(inputDataOffset(frame).intValue(), inputDataLength(frame).intValue()); frame.expandMemory(inputDataOffset(frame), inputDataLength(frame));
frame.expandMemory(outputDataOffset(frame).intValue(), outputDataLength(frame).intValue()); frame.expandMemory(outputDataOffset(frame), outputDataLength(frame));
frame.incrementRemainingGas(gasAvailableForChildCall(frame).plus(cost)); frame.incrementRemainingGas(gasAvailableForChildCall(frame).plus(cost));
frame.popStackItems(getStackItemsConsumed()); frame.popStackItems(getStackItemsConsumed());
frame.pushStackItem(Bytes32.ZERO); frame.pushStackItem(Bytes32.ZERO);
@ -227,7 +227,7 @@ public abstract class AbstractCallOperation extends AbstractOperation {
final int outputSizeAsInt = outputSize.intValue(); final int outputSizeAsInt = outputSize.intValue();
if (outputSizeAsInt > outputData.size()) { if (outputSizeAsInt > outputData.size()) {
frame.expandMemory(outputOffset.intValue(), outputSizeAsInt); frame.expandMemory(outputOffset, outputSize);
frame.writeMemory(outputOffset, UInt256.valueOf(outputData.size()), outputData, true); frame.writeMemory(outputOffset, UInt256.valueOf(outputData.size()), outputData, true);
} else { } else {
frame.writeMemory(outputOffset, outputSize, outputData, true); frame.writeMemory(outputOffset, outputSize, outputData, true);

@ -84,7 +84,7 @@ public class EVM {
logState(frame, result.getGasCost().orElse(Gas.ZERO)); logState(frame, result.getGasCost().orElse(Gas.ZERO));
final Optional<ExceptionalHaltReason> haltReason = result.getHaltReason(); final Optional<ExceptionalHaltReason> haltReason = result.getHaltReason();
if (haltReason.isPresent()) { if (haltReason.isPresent()) {
LOG.trace("MessageFrame evaluation halted because of {}", haltReason); LOG.trace("MessageFrame evaluation halted because of {}", haltReason.get());
frame.setExceptionalHaltReason(haltReason); frame.setExceptionalHaltReason(haltReason);
frame.setState(State.EXCEPTIONAL_HALT); frame.setState(State.EXCEPTIONAL_HALT);
} else if (result.getGasCost().isPresent()) { } else if (result.getGasCost().isPresent()) {

@ -135,16 +135,28 @@ public class Memory {
/** /**
* Expands the active words to accommodate the specified byte position. * Expands the active words to accommodate the specified byte position.
* *
* @param address The location in memory to start with. * @param offset The location in memory to start with.
* @param numBytes The number of bytes to get. * @param numBytes The number of bytes to get.
*/ */
void ensureCapacityForBytes(final int address, final int numBytes) { void ensureCapacityForBytes(final UInt256 offset, final UInt256 numBytes) {
if (!offset.fitsInt()) return;
if (!numBytes.fitsInt()) return;
ensureCapacityForBytes(offset.intValue(), numBytes.intValue());
}
/**
* Expands the active words to accommodate the specified byte position.
*
* @param offset The location in memory to start with.
* @param numBytes The number of bytes to get.
*/
void ensureCapacityForBytes(final int offset, final int numBytes) {
// Do not increase the memory capacity if no bytes are being written // Do not increase the memory capacity if no bytes are being written
// regardless of what the address may be. // regardless of what the address may be.
if (numBytes == 0) { if (numBytes == 0) {
return; return;
} }
final int lastByteIndex = Math.addExact(address, numBytes); final int lastByteIndex = Math.addExact(offset, numBytes);
final int lastWordRequired = ((lastByteIndex - 1) / Bytes32.SIZE); final int lastWordRequired = ((lastByteIndex - 1) / Bytes32.SIZE);
maybeExpandCapacity(lastWordRequired + 1); maybeExpandCapacity(lastWordRequired + 1);
} }

@ -550,7 +550,7 @@ public class MessageFrame {
* @param offset The offset in memory * @param offset The offset in memory
* @param length The length of the memory access * @param length The length of the memory access
*/ */
public void expandMemory(final int offset, final int length) { public void expandMemory(final UInt256 offset, final UInt256 length) {
memory.ensureCapacityForBytes(offset, length); memory.ensureCapacityForBytes(offset, length);
} }

Loading…
Cancel
Save