diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java index 88f2daa8cd..e0041017b8 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java @@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.transaction; import static org.hyperledger.besu.ethereum.goquorum.GoQuorumPrivateStateUtil.getPrivateWorldStateAtBlock; +import org.hyperledger.besu.config.GoQuorumOptions; import org.hyperledger.besu.crypto.SECPSignature; import org.hyperledger.besu.crypto.SignatureAlgorithm; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; @@ -71,6 +72,11 @@ public class TransactionSimulator { private static final Address DEFAULT_FROM = Address.fromHexString("0x0000000000000000000000000000000000000000"); + // Hex-encoded 64 byte array of "17" values + private static final Bytes MAX_PRIVATE_INTRINSIC_DATA_HEX = + Bytes.fromHexString( + "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"); + private final Blockchain blockchain; private final WorldStateArchive worldStateArchive; private final ProtocolSchedule protocolSchedule; @@ -157,12 +163,25 @@ public class TransactionSimulator { callParams.getGasLimit() >= 0 ? callParams.getGasLimit() : header.getGasLimit(); final Wei gasPrice = callParams.getGasPrice() != null ? callParams.getGasPrice() : Wei.ZERO; final Wei value = callParams.getValue() != null ? callParams.getValue() : Wei.ZERO; - final Bytes payload = callParams.getPayload() != null ? callParams.getPayload() : Bytes.EMPTY; + + // If GoQuorum privacy enabled, and value = zero, do simulation with max non-zero bytes. + // It is possible to have a data field that has a lower intrinsic value than the PTM hash + // so this checks the tx as if we were to place a PTM hash (with all non-zero values). + // This means a potential over-estimate of gas, rather than the exact cost to run right now. + final Bytes payload = + GoQuorumOptions.goQuorumCompatibilityMode && value.isZero() + ? MAX_PRIVATE_INTRINSIC_DATA_HEX + : (callParams.getPayload() != null ? callParams.getPayload() : Bytes.EMPTY); if (transactionValidationParams.isAllowExceedingBalance()) { updater.getOrCreate(senderAddress).getMutable().setBalance(Wei.of(UInt256.MAX_VALUE)); } + final ProtocolSpec protocolSpec = protocolSchedule.getByBlockNumber(header.getNumber()); + + final MainnetTransactionProcessor transactionProcessor = + protocolSchedule.getByBlockNumber(header.getNumber()).getTransactionProcessor(); + final Transaction.Builder transactionBuilder = Transaction.builder() .type(TransactionType.FRONTIER) @@ -179,10 +198,6 @@ public class TransactionSimulator { final Transaction transaction = transactionBuilder.build(); - final ProtocolSpec protocolSpec = protocolSchedule.getByBlockNumber(header.getNumber()); - - final MainnetTransactionProcessor transactionProcessor = - protocolSchedule.getByBlockNumber(header.getNumber()).getTransactionProcessor(); final TransactionProcessingResult result = transactionProcessor.processTransaction( blockchain, diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorTest.java index 700d279678..7210e3bf4d 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; +import org.hyperledger.besu.config.GoQuorumOptions; import org.hyperledger.besu.crypto.SECPSignature; import org.hyperledger.besu.crypto.SignatureAlgorithm; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; @@ -53,6 +54,7 @@ import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -75,6 +77,11 @@ public class TransactionSimulatorTest { private static final Hash DEFAULT_BLOCK_HEADER_HASH = Hash.fromHexString("0x0000000000000000000000000000000000000000000000000000000000000001"); + // Hex-encoded 64 byte array of "17" values + private static final Bytes MAX_PRIVATE_INTRINSIC_DATA_HEX = + Bytes.fromHexString( + "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"); + private TransactionSimulator transactionSimulator; @Mock private Blockchain blockchain; @@ -91,6 +98,12 @@ public class TransactionSimulatorTest { new TransactionSimulator(blockchain, worldStateArchive, protocolSchedule); } + @After + public void tearDown() { + GoQuorumOptions.goQuorumCompatibilityMode = + GoQuorumOptions.GOQUORUM_COMPATIBILITY_MODE_DEFAULT_VALUE; + } + @Test public void shouldReturnEmptyWhenBlockDoesNotExist() { when(blockchain.getBlockHeader(eq(1L))).thenReturn(Optional.empty()); @@ -128,6 +141,35 @@ public class TransactionSimulatorTest { verifyTransactionWasProcessed(expectedTransaction); } + @Test + public void shouldReturnSuccessfulResultWhenProcessingPotentiallyPrivateTxIsSuccessful() { + final CallParameter callParameter = legacyTransactionCallParameter(); + + GoQuorumOptions.goQuorumCompatibilityMode = true; + + mockBlockchainForBlockHeader(Hash.ZERO, 1L); + mockWorldStateForAccount(Hash.ZERO, callParameter.getFrom(), 1L); + + final Transaction expectedTransaction = + Transaction.builder() + .nonce(1L) + .gasPrice(callParameter.getGasPrice()) + .gasLimit(callParameter.getGasLimit()) + .to(callParameter.getTo()) + .sender(callParameter.getFrom()) + .value(callParameter.getValue()) + .payload(MAX_PRIVATE_INTRINSIC_DATA_HEX) + .signature(FAKE_SIGNATURE) + .build(); + mockProcessorStatusForTransaction(1L, expectedTransaction, Status.SUCCESSFUL); + + final Optional result = + transactionSimulator.process(callParameter, 1L); + + assertThat(result.get().isSuccessful()).isTrue(); + verifyTransactionWasProcessed(expectedTransaction); + } + @Test public void shouldIncreaseBalanceAccountWhenExceedingBalanceAllowed() { final CallParameter callParameter = legacyTransactionCallParameter();