diff --git a/CHANGELOG.md b/CHANGELOG.md index fe2d341a3d7..8105305c298 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ - Add support for `movePrecompileToAddress` in `StateOverrides` (`eth_call`)[8115](https://github.com/hyperledger/besu/pull/8115) - Default target-gas-limit to 36M for holesky [#8125](https://github.com/hyperledger/besu/pull/8125) - Add EIP-7623 - Increase calldata cost [#8093](https://github.com/hyperledger/besu/pull/8093) +- Add nonce to transaction call object [#8139](https://github.com/hyperledger/besu/pull/8139) ### Bug fixes - Fix serialization of state overrides when `movePrecompileToAddress` is present [#8204](https://github.com/hyperledger/besu/pull/8024) diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthEstimateGasTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthEstimateGasTransaction.java index 31e0c518200..ba4b046aa32 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthEstimateGasTransaction.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthEstimateGasTransaction.java @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.tests.acceptance.dsl.transaction.eth; +import static org.web3j.protocol.core.DefaultBlockParameterName.LATEST; + import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; @@ -36,11 +38,13 @@ public EthEstimateGasTransaction(final String contractAddress, final String func public EthEstimateGas execute(final NodeRequests node) { try { + var nonce = node.eth().ethGetTransactionCount(from, LATEST).send().getTransactionCount(); + return node.eth() .ethEstimateGas( new org.web3j.protocol.core.methods.request.Transaction( from, - BigInteger.ONE, + nonce, BigInteger.ZERO, BigInteger.ZERO, contractAddress, diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/BlockAdapterBase.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/BlockAdapterBase.java index f49fa0a4c8f..dc3f00bdc89 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/BlockAdapterBase.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/BlockAdapterBase.java @@ -356,6 +356,7 @@ private Optional executeCall(final DataFetchingEnvironment environme maxFeePerGas, valueParam, data, + Optional.empty(), Optional.empty()); return transactionSimulator.process( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java index d839f2ed796..9e380a4c17b 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java @@ -147,7 +147,8 @@ protected CallParameter overrideGasLimit(final CallParameter callParams, final l callParams.getPayload(), callParams.getAccessList(), callParams.getMaxFeePerBlobGas(), - callParams.getBlobVersionedHashes()); + callParams.getBlobVersionedHashes(), + callParams.getNonce()); } /** diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessList.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessList.java index 7d4ba313cf1..d9394417abd 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessList.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessList.java @@ -127,7 +127,8 @@ private CallParameter overrideAccessList( callParams.getMaxFeePerGas(), callParams.getValue(), callParams.getPayload(), - Optional.of(accessListEntries)); + Optional.of(accessListEntries), + callParams.getNonce()); } private record AccessListSimulatorResult( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/JsonCallParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/JsonCallParameter.java index 85dabda5719..dba20335a22 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/JsonCallParameter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/JsonCallParameter.java @@ -18,6 +18,7 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.VersionedHash; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.datatypes.parameters.UnsignedLongParameter; import org.hyperledger.besu.ethereum.core.json.ChainIdDeserializer; import org.hyperledger.besu.ethereum.core.json.GasDeserializer; import org.hyperledger.besu.ethereum.core.json.HexStringDeserializer; @@ -56,6 +57,7 @@ * .withMaxPriorityFeePerGas(Wei.of(1)) // Optional * .withMaxFeePerBlobGas(Wei.of(3)) // Optional * .withBlobVersionedHashes(blobVersionedHashes) // Optional + * .withNonce(new UnsignedLongParameter(1L)) // Optional * .build(); * } * @@ -86,7 +88,8 @@ private JsonCallParameter( final Optional strict, final Optional> accessList, final Optional maxFeePerBlobGas, - final Optional> blobVersionedHashes) { + final Optional> blobVersionedHashes, + final Optional nonce) { super( chainId, @@ -100,7 +103,8 @@ private JsonCallParameter( payload, accessList, maxFeePerBlobGas, - blobVersionedHashes); + blobVersionedHashes, + nonce); this.strict = strict; } @@ -133,6 +137,7 @@ public static final class JsonCallParameterBuilder { private Bytes input; private Optional> accessList = Optional.empty(); private Optional> blobVersionedHashes = Optional.empty(); + private Optional nonce = Optional.empty(); /** Default constructor. */ public JsonCallParameterBuilder() {} @@ -327,6 +332,18 @@ public JsonCallParameterBuilder withBlobVersionedHashes( return this; } + /** + * Sets the nonce for the {@link JsonCallParameter}. It is an optional parameter, and if not + * specified, it defaults to an empty {@link Optional}. + * + * @param nonce the nonce, can be {@code null} to indicate no nonce is provided + * @return the {@link JsonCallParameterBuilder} instance for chaining + */ + public JsonCallParameterBuilder withNonce(final UnsignedLongParameter nonce) { + this.nonce = Optional.ofNullable(nonce).map(UnsignedLongParameter::getValue); + return this; + } + /** * Handles unknown JSON properties during deserialization. This method is invoked when an * unknown property is encountered in the JSON being deserialized into a {@link @@ -376,7 +393,8 @@ public JsonCallParameter build() { strict, accessList, maxFeePerBlobGas, - blobVersionedHashes); + blobVersionedHashes, + nonce); } } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java index 880ddb9a1fb..eea8f55b268 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java @@ -579,6 +579,7 @@ private CallParameter modifiedLegacyTransactionCallParameter(final Wei gasPrice) Optional.empty(), Wei.ZERO, Bytes.EMPTY, + Optional.empty(), Optional.empty()); } @@ -613,6 +614,7 @@ private CallParameter modifiedEip1559TransactionCallParameter(final Optional> accessList; private final Optional> blobVersionedHashes; + private final Optional nonce; public CallParameter( final Address from, @@ -70,6 +71,30 @@ public CallParameter( this.payload = payload; this.maxFeePerBlobGas = Optional.empty(); this.blobVersionedHashes = Optional.empty(); + this.nonce = Optional.empty(); + } + + public CallParameter( + final Address from, + final Address to, + final long gasLimit, + final Wei gasPrice, + final Wei value, + final Bytes payload, + final Optional nonce) { + this.chainId = Optional.empty(); + this.from = from; + this.to = to; + this.gasLimit = gasLimit; + this.accessList = Optional.empty(); + this.maxPriorityFeePerGas = Optional.empty(); + this.maxFeePerGas = Optional.empty(); + this.gasPrice = gasPrice; + this.value = value; + this.payload = payload; + this.maxFeePerBlobGas = Optional.empty(); + this.blobVersionedHashes = Optional.empty(); + this.nonce = nonce; } public CallParameter( @@ -81,7 +106,8 @@ public CallParameter( final Optional maxFeePerGas, final Wei value, final Bytes payload, - final Optional> accessList) { + final Optional> accessList, + final Optional nonce) { this.chainId = Optional.empty(); this.from = from; this.to = to; @@ -94,6 +120,7 @@ public CallParameter( this.accessList = accessList; this.maxFeePerBlobGas = Optional.empty(); this.blobVersionedHashes = Optional.empty(); + this.nonce = nonce; } public CallParameter( @@ -108,7 +135,8 @@ public CallParameter( final Bytes payload, final Optional> accessList, final Optional maxFeePerBlobGas, - final Optional> blobVersionedHashes) { + final Optional> blobVersionedHashes, + final Optional nonce) { this.chainId = chainId; this.from = from; this.to = to; @@ -121,6 +149,7 @@ public CallParameter( this.accessList = accessList; this.maxFeePerBlobGas = maxFeePerBlobGas; this.blobVersionedHashes = blobVersionedHashes; + this.nonce = nonce; } public Optional getChainId() { @@ -171,6 +200,10 @@ public Optional> getBlobVersionedHashes() { return blobVersionedHashes; } + public Optional getNonce() { + return nonce; + } + @Override public boolean equals(final Object o) { if (this == o) { @@ -191,7 +224,8 @@ public boolean equals(final Object o) { && Objects.equals(payload, that.payload) && Objects.equals(accessList, that.accessList) && Objects.equals(maxFeePerBlobGas, that.maxFeePerBlobGas) - && Objects.equals(blobVersionedHashes, that.blobVersionedHashes); + && Objects.equals(blobVersionedHashes, that.blobVersionedHashes) + && Objects.equals(nonce, that.nonce); } @Override @@ -208,7 +242,8 @@ public int hashCode() { payload, accessList, maxFeePerBlobGas, - blobVersionedHashes); + blobVersionedHashes, + nonce); } @Override @@ -238,6 +273,8 @@ public String toString() { + accessList.map(List::size) + ", blobVersionedHashesSize=" + blobVersionedHashes.map(List::size) + + ", nonce=" + + nonce.map(Object::toString).orElse("N/A") + '}'; } @@ -254,7 +291,8 @@ public static CallParameter fromTransaction(final Transaction tx) { tx.getPayload(), tx.getAccessList(), tx.getMaxFeePerBlobGas(), - tx.getVersionedHashes()); + tx.getVersionedHashes(), + Optional.of(tx.getNonce())); } public static CallParameter fromTransaction(final org.hyperledger.besu.datatypes.Transaction tx) { @@ -270,6 +308,7 @@ public static CallParameter fromTransaction(final org.hyperledger.besu.datatypes tx.getPayload(), tx.getAccessList(), tx.getMaxFeePerBlobGas().map(Wei::fromQuantity), - tx.getVersionedHashes()); + tx.getVersionedHashes(), + Optional.of(tx.getNonce())); } } 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 cc0f77bb2dc..7615f7e3708 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 @@ -399,8 +399,11 @@ public Optional processWithWorldUpdater( } } - final Account sender = updater.get(senderAddress); - final long nonce = sender != null ? sender.getNonce() : 0L; + final long nonce = + callParams + .getNonce() + .orElse( + Optional.ofNullable(updater.get(senderAddress)).map(Account::getNonce).orElse(0L)); final long simulationGasCap = calculateSimulationGasCap(callParams.getGasLimit(), blockHeaderToProcess.getGasLimit()); @@ -635,4 +638,8 @@ private boolean shouldSetBlobGasPrice(final CallParameter callParams) { } return callParams.getBlobVersionedHashes().isPresent(); } + + public WorldStateArchive getWorldStateArchive() { + return worldStateArchive; + } } 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 f0e32ead379..43d44911b13 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 @@ -34,7 +34,9 @@ import org.hyperledger.besu.datatypes.StateOverride; 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; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonCallParameter.JsonCallParameterBuilder; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlobTestFixture; import org.hyperledger.besu.ethereum.core.BlockHeader; @@ -153,20 +155,19 @@ public void shouldReturnEmptyWhenBlockDoesNotExist() { when(blockchain.getBlockHeader(eq(1L))).thenReturn(Optional.empty()); final Optional result = - transactionSimulator.process(legacyTransactionCallParameter(), 1L); + transactionSimulator.process(legacyTransactionCallParameterBuilder().build(), 1L); assertThat(result.isPresent()).isFalse(); } @Test public void shouldReturnSuccessfulResultWhenProcessingIsSuccessful() { - final CallParameter callParameter = legacyTransactionCallParameter(); + final CallParameter callParameter = legacyTransactionCallParameterBuilder().build(); final BlockHeader blockHeader = blockHeaderTestFixture.number(1L).stateRoot(Hash.ZERO).buildHeader(); - mockBlockchainForBlockHeader(blockHeader); - mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L); + mockBlockchainAndWorldState(callParameter); final Transaction expectedTransaction = Transaction.builder() @@ -191,12 +192,11 @@ public void shouldReturnSuccessfulResultWhenProcessingIsSuccessful() { @Test public void simulateOnPendingBlockWorks() { - final CallParameter callParameter = eip1559TransactionCallParameter(); + final CallParameter callParameter = eip1559TransactionCallParameterBuilder().build(); final BlockHeader blockHeader = mockBlockHeader(Hash.ZERO, 1L, Wei.ONE); - mockBlockchainForBlockHeader(blockHeader); - mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L); + mockBlockchainAndWorldState(callParameter); final Transaction expectedTransaction = Transaction.builder() @@ -228,13 +228,13 @@ public void simulateOnPendingBlockWorks() { @Test public void shouldSetGasPriceToZeroWhenExceedingBalanceAllowed() { - final CallParameter callParameter = legacyTransactionCallParameter(Wei.ONE); + final CallParameter callParameter = + legacyTransactionCallParameterBuilder().withGasPrice(Wei.ONE).build(); final BlockHeader blockHeader = blockHeaderTestFixture.number(1L).stateRoot(Hash.ZERO).buildHeader(); - mockBlockchainForBlockHeader(blockHeader); - mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L); + mockBlockchainAndWorldState(callParameter); final Transaction expectedTransaction = Transaction.builder() @@ -262,12 +262,13 @@ public void shouldSetGasPriceToZeroWhenExceedingBalanceAllowed() { @Test public void shouldSetFeePerGasToZeroWhenExceedingBalanceAllowed() { final CallParameter callParameter = - eip1559TransactionCallParameter(Wei.ONE, Wei.ONE, TRANSFER_GAS_LIMIT); - - final BlockHeader blockHeader = mockBlockHeader(Hash.ZERO, 1L, Wei.ONE); + eip1559TransactionCallParameterBuilder() + .withMaxFeePerGas(Wei.ONE) + .withMaxPriorityFeePerGas(Wei.ONE) + .withGas(TRANSFER_GAS_LIMIT) + .build(); - mockBlockchainForBlockHeader(blockHeader); - mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L); + mockBlockchainAndWorldState(callParameter); final Transaction expectedTransaction = Transaction.builder() @@ -297,13 +298,13 @@ public void shouldSetFeePerGasToZeroWhenExceedingBalanceAllowed() { @Test public void shouldNotSetGasPriceToZeroWhenExceedingBalanceIsNotAllowed() { - final CallParameter callParameter = legacyTransactionCallParameter(Wei.ONE); + final CallParameter callParameter = + legacyTransactionCallParameterBuilder().withGasPrice(Wei.ONE).build(); final BlockHeader blockHeader = blockHeaderTestFixture.number(1L).stateRoot(Hash.ZERO).buildHeader(); - mockBlockchainForBlockHeader(blockHeader); - mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L); + mockBlockchainAndWorldState(callParameter); final Transaction expectedTransaction = Transaction.builder() @@ -332,12 +333,13 @@ public void shouldNotSetGasPriceToZeroWhenExceedingBalanceIsNotAllowed() { @Test public void shouldNotSetFeePerGasToZeroWhenExceedingBalanceIsNotAllowed() { final CallParameter callParameter = - eip1559TransactionCallParameter(Wei.ONE, Wei.ONE, TRANSFER_GAS_LIMIT); + eip1559TransactionCallParameterBuilder() + .withMaxFeePerGas(Wei.ONE) + .withMaxPriorityFeePerGas(Wei.ONE) + .withGas(TRANSFER_GAS_LIMIT) + .build(); - final BlockHeader blockHeader = mockBlockHeader(Hash.ZERO, 1L, Wei.ONE); - - mockBlockchainForBlockHeader(blockHeader); - mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L); + mockBlockchainAndWorldState(callParameter); final Transaction expectedTransaction = Transaction.builder() @@ -366,7 +368,7 @@ public void shouldNotSetFeePerGasToZeroWhenExceedingBalanceIsNotAllowed() { @Test public void shouldUseDefaultValuesWhenMissingOptionalFields() { - final CallParameter callParameter = legacyTransactionCallParameter(); + final CallParameter callParameter = legacyTransactionCallParameterBuilder().build(); final BlockHeader blockHeader = blockHeaderTestFixture.number(1L).stateRoot(Hash.ZERO).buildHeader(); @@ -395,7 +397,7 @@ public void shouldUseDefaultValuesWhenMissingOptionalFields() { @Test public void shouldUseZeroNonceWhenAccountDoesNotExist() { - final CallParameter callParameter = legacyTransactionCallParameter(); + final CallParameter callParameter = legacyTransactionCallParameterBuilder().build(); final BlockHeader blockHeader = blockHeaderTestFixture.number(1L).stateRoot(Hash.ZERO).buildHeader(); @@ -422,9 +424,42 @@ public void shouldUseZeroNonceWhenAccountDoesNotExist() { verifyTransactionWasProcessed(expectedTransaction); } + @Test + public void shouldUseSpecifiedNonceWhenProvided() { + long expectedNonce = 2L; + long accountNonce = 1L; + final CallParameter callParameter = + eip1559TransactionCallParameterBuilder() + .withNonce(new UnsignedLongParameter(expectedNonce)) + .build(); + + final BlockHeader blockHeader = + blockHeaderTestFixture.number(1L).stateRoot(Hash.ZERO).buildHeader(); + + mockBlockchainForBlockHeader(blockHeader); + mockWorldStateForAccount(blockHeader, Address.fromHexString("0x0"), accountNonce); + + final Transaction expectedTransaction = + Transaction.builder() + .type(TransactionType.FRONTIER) + .nonce(expectedNonce) + .gasPrice(Wei.ZERO) + .gasLimit(0L) + .to(DEFAULT_FROM) + .sender(Address.fromHexString("0x0")) + .value(Wei.ZERO) + .payload(Bytes.EMPTY) + .signature(FAKE_SIGNATURE) + .build(); + mockProcessorStatusForTransaction(expectedTransaction, Status.SUCCESSFUL); + + transactionSimulator.process(callParameter, 1L); + verifyTransactionWasProcessed(expectedTransaction); + } + @Test public void shouldReturnFailureResultWhenProcessingFails() { - final CallParameter callParameter = legacyTransactionCallParameter(); + final CallParameter callParameter = legacyTransactionCallParameterBuilder().build(); final BlockHeader blockHeader = blockHeaderTestFixture.number(1L).stateRoot(Hash.ZERO).buildHeader(); @@ -458,14 +493,14 @@ public void shouldReturnEmptyWhenBlockDoesNotExistByHash() { when(blockchain.getBlockHeader(eq(Hash.ZERO))).thenReturn(Optional.empty()); final Optional result = - transactionSimulator.process(legacyTransactionCallParameter(), Hash.ZERO); + transactionSimulator.process(legacyTransactionCallParameterBuilder().build(), Hash.ZERO); assertThat(result.isPresent()).isFalse(); } @Test public void shouldReturnSuccessfulResultWhenProcessingIsSuccessfulByHash() { - final CallParameter callParameter = legacyTransactionCallParameter(); + final CallParameter callParameter = legacyTransactionCallParameterBuilder().build(); final BlockHeader blockHeader = blockHeaderTestFixture.number(1L).stateRoot(Hash.ZERO).buildHeader(); @@ -496,7 +531,7 @@ public void shouldReturnSuccessfulResultWhenProcessingIsSuccessfulByHash() { @Test public void shouldUseDefaultValuesWhenMissingOptionalFieldsByHash() { - final CallParameter callParameter = legacyTransactionCallParameter(); + final CallParameter callParameter = legacyTransactionCallParameterBuilder().build(); final BlockHeader blockHeader = blockHeaderTestFixture.number(1L).stateRoot(Hash.ZERO).buildHeader(); @@ -525,7 +560,7 @@ public void shouldUseDefaultValuesWhenMissingOptionalFieldsByHash() { @Test public void shouldUseZeroNonceWhenAccountDoesNotExistByHash() { - final CallParameter callParameter = legacyTransactionCallParameter(); + final CallParameter callParameter = legacyTransactionCallParameterBuilder().build(); final BlockHeader blockHeader = blockHeaderTestFixture.number(1L).stateRoot(Hash.ZERO).buildHeader(); @@ -554,7 +589,7 @@ public void shouldUseZeroNonceWhenAccountDoesNotExistByHash() { @Test public void shouldReturnFailureResultWhenProcessingFailsByHash() { - final CallParameter callParameter = legacyTransactionCallParameter(); + final CallParameter callParameter = legacyTransactionCallParameterBuilder().build(); final BlockHeader blockHeader = blockHeaderTestFixture.number(1L).stateRoot(Hash.ZERO).buildHeader(); @@ -585,12 +620,11 @@ public void shouldReturnFailureResultWhenProcessingFailsByHash() { @Test public void shouldReturnSuccessfulResultWhenEip1559TransactionProcessingIsSuccessful() { - final CallParameter callParameter = eip1559TransactionCallParameter(); + final CallParameter callParameter = eip1559TransactionCallParameterBuilder().build(); final BlockHeader blockHeader = mockBlockHeader(Hash.ZERO, 1L, Wei.ONE); - mockBlockchainForBlockHeader(blockHeader); - mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L); + mockBlockchainAndWorldState(callParameter); final Transaction expectedTransaction = Transaction.builder() @@ -618,12 +652,9 @@ public void shouldReturnSuccessfulResultWhenEip1559TransactionProcessingIsSucces @Test public void shouldCapGasLimitWhenOriginalTransactionExceedsGasCap() { final CallParameter callParameter = - eip1559TransactionCallParameter(Wei.ZERO, Wei.ZERO, GAS_CAP + 1); + eip1559TransactionCallParameterBuilder().withGas(GAS_CAP + 1).build(); - final BlockHeader blockHeader = mockBlockHeader(Hash.ZERO, 1L, Wei.ONE); - - mockBlockchainForBlockHeader(blockHeader); - mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L); + mockBlockchainAndWorldState(callParameter); final Transaction expectedTransaction = Transaction.builder() @@ -653,12 +684,11 @@ public void shouldCapGasLimitWhenOriginalTransactionExceedsGasCap() { @Test public void shouldUseProvidedGasLimitWhenBelowRpcCapGas() { final CallParameter callParameter = - eip1559TransactionCallParameter(Wei.ZERO, Wei.ZERO, GAS_CAP / 2); + eip1559TransactionCallParameterBuilder().withGas(GAS_CAP / 2).build(); - final BlockHeader blockHeader = mockBlockHeader(Hash.ZERO, 1L, Wei.ONE); + mockBlockHeader(Hash.ZERO, 1L, Wei.ONE); - mockBlockchainForBlockHeader(blockHeader); - mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L); + mockBlockchainAndWorldState(callParameter); final Transaction expectedTransaction = Transaction.builder() @@ -689,12 +719,11 @@ public void shouldUseProvidedGasLimitWhenBelowRpcCapGas() { public void shouldUseRpcGasCapWhenGasLimitNoPresent() { // generate call parameters that do not specify a gas limit, // expect the rpc gas cap to be used for simulation - final CallParameter callParameter = eip1559TransactionCallParameter(Wei.ZERO, Wei.ZERO, -1); - final BlockHeader blockHeader = mockBlockHeader(Hash.ZERO, 1L, Wei.ONE); + final CallParameter callParameter = + eip1559TransactionCallParameterBuilder().withGas(-1L).build(); - mockBlockchainForBlockHeader(blockHeader); - mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L); + mockBlockchainAndWorldState(callParameter); mockProtocolSpecForProcessWithWorldUpdater(); final Transaction expectedTransaction = @@ -723,34 +752,11 @@ public void shouldUseRpcGasCapWhenGasLimitNoPresent() { @Test public void shouldReturnSuccessfulResultWhenBlobTransactionProcessingIsSuccessful() { - final CallParameter callParameter = - blobTransactionCallParameter(Wei.ONE, Wei.ONE, Wei.ONE, 300, 3); - - final BlockHeader blockHeader = mockBlockHeader(Hash.ZERO, 1L, Wei.ONE); + final CallParameter callParameter = blobTransactionCallParameter(); + mockBlockchainAndWorldState(callParameter); - mockBlockchainForBlockHeader(blockHeader); - mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L); - - final Transaction expectedTransaction = - Transaction.builder() - .type(TransactionType.BLOB) - .chainId(BigInteger.ONE) - .nonce(1L) - .gasLimit(callParameter.getGasLimit()) - .maxFeePerGas(callParameter.getMaxFeePerGas().orElseThrow()) - .maxPriorityFeePerGas(callParameter.getMaxPriorityFeePerGas().orElseThrow()) - .to(callParameter.getTo()) - .sender(callParameter.getFrom()) - .value(callParameter.getValue()) - .payload(callParameter.getPayload()) - .maxFeePerBlobGas(callParameter.getMaxFeePerBlobGas().get()) - .versionedHashes(callParameter.getBlobVersionedHashes().get()) - .signature(FAKE_SIGNATURE) - .build(); - - final CallParameter reverseEngineeredCallParam = - CallParameter.fromTransaction(expectedTransaction); - assertThat(reverseEngineeredCallParam).isEqualTo(callParameter); + final Transaction expectedTransaction = buildExpectedTransaction(callParameter); + assertCallParametersEqual(callParameter, CallParameter.fromTransaction(expectedTransaction)); mockProcessorStatusForTransaction(expectedTransaction, Status.SUCCESSFUL); @@ -763,34 +769,11 @@ public void shouldReturnSuccessfulResultWhenBlobTransactionProcessingIsSuccessfu @Test public void shouldReturnFailureResultWhenBlobTransactionProcessingFails() { - final CallParameter callParameter = - blobTransactionCallParameter(Wei.ONE, Wei.ONE, Wei.ONE, 300, 3); + final CallParameter callParameter = blobTransactionCallParameter(); + mockBlockchainAndWorldState(callParameter); - final BlockHeader blockHeader = mockBlockHeader(Hash.ZERO, 1L, Wei.ONE); - - mockBlockchainForBlockHeader(blockHeader); - mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L); - - final Transaction expectedTransaction = - Transaction.builder() - .type(TransactionType.BLOB) - .chainId(BigInteger.ONE) - .nonce(1L) - .gasLimit(callParameter.getGasLimit()) - .maxFeePerGas(callParameter.getMaxFeePerGas().orElseThrow()) - .maxPriorityFeePerGas(callParameter.getMaxPriorityFeePerGas().orElseThrow()) - .to(callParameter.getTo()) - .sender(callParameter.getFrom()) - .value(callParameter.getValue()) - .payload(callParameter.getPayload()) - .maxFeePerBlobGas(callParameter.getMaxFeePerBlobGas().get()) - .versionedHashes(callParameter.getBlobVersionedHashes().get()) - .signature(FAKE_SIGNATURE) - .build(); - - final CallParameter reverseEngineeredCallParam = - CallParameter.fromTransaction(expectedTransaction); - assertThat(reverseEngineeredCallParam).isEqualTo(callParameter); + final Transaction expectedTransaction = buildExpectedTransaction(callParameter); + assertCallParametersEqual(callParameter, CallParameter.fromTransaction(expectedTransaction)); mockProcessorStatusForTransaction(expectedTransaction, Status.FAILED); @@ -801,6 +784,30 @@ public void shouldReturnFailureResultWhenBlobTransactionProcessingFails() { verifyTransactionWasProcessed(expectedTransaction); } + private void mockBlockchainAndWorldState(final CallParameter callParameter) { + final BlockHeader blockHeader = mockBlockHeader(Hash.ZERO, 1L, Wei.ONE); + mockBlockchainForBlockHeader(blockHeader); + mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L); + } + + private Transaction buildExpectedTransaction(final CallParameter callParameter) { + return Transaction.builder() + .type(TransactionType.BLOB) + .chainId(callParameter.getChainId().orElseThrow()) + .nonce(callParameter.getNonce().orElseThrow()) + .gasLimit(callParameter.getGasLimit()) + .maxFeePerGas(callParameter.getMaxFeePerGas().orElseThrow()) + .maxPriorityFeePerGas(callParameter.getMaxPriorityFeePerGas().orElseThrow()) + .to(callParameter.getTo()) + .sender(callParameter.getFrom()) + .value(callParameter.getValue()) + .payload(callParameter.getPayload()) + .maxFeePerBlobGas(callParameter.getMaxFeePerBlobGas().orElseThrow()) + .versionedHashes(callParameter.getBlobVersionedHashes().orElseThrow()) + .signature(FAKE_SIGNATURE) + .build(); + } + private void mockWorldStateForAccount( final BlockHeader blockHeader, final Address address, final long nonce) { final Account account = mock(Account.class); @@ -895,72 +902,40 @@ private void verifyTransactionWasProcessed(final Transaction expectedTransaction any(Wei.class)); } - private CallParameter legacyTransactionCallParameter() { - return legacyTransactionCallParameter(Wei.ZERO); - } - - private CallParameter legacyTransactionCallParameter(final Wei gasPrice) { - return new CallParameter( - Address.fromHexString("0x0"), - Address.fromHexString("0x0"), - -1, - gasPrice, - Wei.of(0), - Bytes.EMPTY); - } - - private CallParameter eip1559TransactionCallParameter() { - return eip1559TransactionCallParameter(Wei.ZERO, Wei.ZERO); - } - - private CallParameter eip1559TransactionCallParameter( - final Wei maxFeePerGas, final Wei maxPriorityFeePerGas) { - return eip1559TransactionCallParameter(maxFeePerGas, maxPriorityFeePerGas, -1); - } - - private CallParameter eip1559TransactionCallParameter( - final Wei maxFeePerGas, final Wei maxPriorityFeePerGas, final long gasLimit) { - return new CallParameter( - Address.fromHexString("0x0"), - Address.fromHexString("0x0"), - gasLimit, - Wei.of(0), - Optional.of(maxFeePerGas), - Optional.of(maxPriorityFeePerGas), - Wei.of(0), - Bytes.EMPTY, - Optional.empty()); - } - - private CallParameter blobTransactionCallParameter( - final Wei maxFeePerBlobGas, - final Wei maxFeePerGas, - final Wei maxPriorityFeePerGas, - final long gasLimit, - final int numberOfBlobs) { - BlobsWithCommitments bwc = new BlobTestFixture().createBlobsWithCommitments(numberOfBlobs); - return new CallParameter( - Optional.of(BigInteger.ONE), - Address.fromHexString("0x0"), - Address.fromHexString("0x0"), - gasLimit, - Wei.of(0), - Optional.of(maxFeePerGas), - Optional.of(maxPriorityFeePerGas), - Wei.of(0), - Bytes.EMPTY, - Optional.empty(), - Optional.of(maxFeePerBlobGas), - Optional.of(bwc.getVersionedHashes())); + private JsonCallParameterBuilder legacyTransactionCallParameterBuilder() { + return new JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0x0")) + .withTo(Address.fromHexString("0x0")) + .withGas(-1L) + .withGasPrice(Wei.ZERO) + .withValue(Wei.ZERO) + .withInput(Bytes.EMPTY); + } + + private JsonCallParameterBuilder eip1559TransactionCallParameterBuilder() { + return legacyTransactionCallParameterBuilder() + .withMaxFeePerGas(Wei.ZERO) + .withMaxPriorityFeePerGas(Wei.ZERO); + } + + private CallParameter blobTransactionCallParameter() { + BlobsWithCommitments bwc = new BlobTestFixture().createBlobsWithCommitments(3); + return eip1559TransactionCallParameterBuilder() + .withNonce(new UnsignedLongParameter(1L)) + .withChainId(BigInteger.ONE) + .withMaxFeePerGas(Wei.ZERO) + .withMaxPriorityFeePerGas(Wei.ZERO) + .withMaxFeePerBlobGas(Wei.ZERO) + .withGas(0L) + .withBlobVersionedHashes(bwc.getVersionedHashes()) + .build(); } @Test public void shouldSimulateLegacyTransactionWhenBaseFeeNotZero() { // tests that the transaction simulator will simulate a legacy transaction when the base fee is - // not zero - // and the transaction is a legacy transaction - - final CallParameter callParameter = legacyTransactionCallParameter(); + // not zero and the transaction is a legacy transaction + final CallParameter callParameter = legacyTransactionCallParameterBuilder().build(); final BlockHeader blockHeader = blockHeaderTestFixture @@ -969,8 +944,7 @@ public void shouldSimulateLegacyTransactionWhenBaseFeeNotZero() { .baseFeePerGas(Wei.of(7)) .buildHeader(); - mockBlockchainForBlockHeader(blockHeader); - mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L); + mockBlockchainAndWorldState(callParameter); final Transaction expectedTransaction = Transaction.builder() @@ -992,4 +966,20 @@ public void shouldSimulateLegacyTransactionWhenBaseFeeNotZero() { verifyTransactionWasProcessed(expectedTransaction); assertThat(result.get().isSuccessful()).isTrue(); } + + private void assertCallParametersEqual(final CallParameter expected, final CallParameter actual) { + assertThat(actual.getChainId()).isEqualTo(expected.getChainId()); + assertThat(actual.getFrom()).isEqualTo(expected.getFrom()); + assertThat(actual.getTo()).isEqualTo(expected.getTo()); + assertThat(actual.getGasLimit()).isEqualTo(expected.getGasLimit()); + assertThat(actual.getGasPrice()).isEqualTo(expected.getGasPrice()); + assertThat(actual.getMaxPriorityFeePerGas()).isEqualTo(expected.getMaxPriorityFeePerGas()); + assertThat(actual.getMaxFeePerGas()).isEqualTo(expected.getMaxFeePerGas()); + assertThat(actual.getValue()).isEqualTo(expected.getValue()); + assertThat(actual.getPayload()).isEqualTo(expected.getPayload()); + assertThat(actual.getAccessList()).isEqualTo(expected.getAccessList()); + assertThat(actual.getMaxFeePerBlobGas()).isEqualTo(expected.getMaxFeePerBlobGas()); + assertThat(actual.getBlobVersionedHashes()).isEqualTo(expected.getBlobVersionedHashes()); + assertThat(actual.getNonce()).isEqualTo(expected.getNonce()); + } }