diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/StateOverride.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/StateOverride.java index 8fdd66425f7..0f85c40b20e 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/StateOverride.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/StateOverride.java @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.datatypes; +import static com.google.common.base.Preconditions.checkState; + import org.hyperledger.besu.datatypes.parameters.UnsignedLongParameter; import java.util.Map; @@ -35,6 +37,7 @@ public class StateOverride { private final Optional balance; private final Optional nonce; private final Optional code; + private final Optional> state; private final Optional> stateDiff; private final Optional
movePrecompileToAddress; @@ -42,11 +45,13 @@ private StateOverride( final Optional balance, final Optional nonce, final Optional code, + final Optional> state, final Optional> stateDiff, final Optional
movePrecompileToAddress) { this.balance = balance; this.nonce = nonce; this.code = code; + this.state = state; this.stateDiff = stateDiff; this.movePrecompileToAddress = movePrecompileToAddress; } @@ -83,6 +88,15 @@ public Optional getCode() { * * @return the state override map if present */ + public Optional> getState() { + return state; + } + + /** + * Gets the state diff override map + * + * @return the state diff override map if present + */ public Optional> getStateDiff() { return stateDiff; } @@ -102,6 +116,7 @@ public static class Builder { private Optional balance = Optional.empty(); private Optional nonce = Optional.empty(); private Optional code = Optional.empty(); + private Optional> state = Optional.empty(); private Optional> stateDiff = Optional.empty(); private Optional
movePrecompileToAddress = Optional.empty(); @@ -141,6 +156,17 @@ public Builder withCode(final String code) { return this; } + /** + * Sets the state override + * + * @param state the map of state overrides + * @return the builder + */ + public Builder withState(final Map state) { + this.state = Optional.ofNullable(state); + return this; + } + /** * Sets the state diff override * @@ -169,7 +195,8 @@ public Builder withMovePrecompileToAddress(final Address newPrecompileAddress) { * @return account override */ public StateOverride build() { - return new StateOverride(balance, nonce, code, stateDiff, movePrecompileToAddress); + checkState(state.isEmpty() || stateDiff.isEmpty(), "Cannot set both state and stateDiff"); + return new StateOverride(balance, nonce, code, state, stateDiff, movePrecompileToAddress); } } @@ -200,12 +227,13 @@ public boolean equals(final Object o) { return balance.equals(stateOverride.balance) && nonce.equals(stateOverride.nonce) && code.equals(stateOverride.code) + && state.equals(stateOverride.state) && stateDiff.equals(stateOverride.stateDiff); } @Override public int hashCode() { - return Objects.hash(balance, nonce, code, stateDiff); + return Objects.hash(balance, nonce, code, state, stateDiff); } @Override @@ -217,6 +245,8 @@ public String toString() { + nonce + ", code=" + code + + ", state=" + + state + ", stateDiff=" + stateDiff + ", movePrecompileToAddress=" diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCallTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCallTest.java index d9232bd0452..8f1e3d07ab8 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCallTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCallTest.java @@ -124,6 +124,27 @@ public void someStateOverrides() { assertThat(overrideMap).containsValue(override); } + @Test + public void stateOverridesWithState() { + StateOverrideMap expectedOverrides = new StateOverrideMap(); + StateOverride override = + new StateOverride.Builder().withState(Map.of("0x1234", "0x5678")).build(); + final Address address = Address.fromHexString("0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3"); + expectedOverrides.put(address, override); + + final JsonRpcRequestContext request = + ethCallRequestWithStateOverrides(callParameter(), "latest", expectedOverrides); + + Optional maybeOverrideMap = method.getAddressStateOverrideMap(request); + assertThat(maybeOverrideMap.isPresent()).isTrue(); + StateOverrideMap overrideMap = maybeOverrideMap.get(); + assertThat(overrideMap.keySet()).hasSize(1); + assertThat(overrideMap.values()).hasSize(1); + + assertThat(overrideMap).containsKey(address); + assertThat(overrideMap).containsValue(override); + } + @Test public void fullStateOverrides() { StateOverrideMap suppliedOverrides = new StateOverrideMap(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/BlockSimulator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/BlockSimulator.java index b82ee1dfa06..bc94177afef 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/BlockSimulator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/BlockSimulator.java @@ -54,7 +54,6 @@ import com.google.common.annotations.VisibleForTesting; import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.units.bigints.UInt256; /** * Simulates the execution of a block, processing transactions and applying state overrides. This @@ -248,19 +247,7 @@ protected void applyStateOverrides( for (Address accountToOverride : stateOverrideMap.keySet()) { final StateOverride override = stateOverrideMap.get(accountToOverride); MutableAccount account = updater.getOrCreate(accountToOverride); - override.getNonce().ifPresent(account::setNonce); - if (override.getBalance().isPresent()) { - account.setBalance(override.getBalance().get()); - } - override.getCode().ifPresent(n -> account.setCode(Bytes.fromHexString(n))); - override - .getStateDiff() - .ifPresent( - d -> - d.forEach( - (key, value) -> - account.setStorageValue( - UInt256.fromHexString(key), UInt256.fromHexString(value)))); + TransactionSimulator.applyOverrides(account, override); } updater.commit(); } 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 7c089c5aa8b..09ceab09a40 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 @@ -460,13 +460,21 @@ public Optional processWithWorldUpdater( } @VisibleForTesting - protected void applyOverrides(final MutableAccount account, final StateOverride override) { + protected static void applyOverrides(final MutableAccount account, final StateOverride override) { LOG.debug("applying overrides to state for account {}", account.getAddress()); override.getNonce().ifPresent(account::setNonce); - if (override.getBalance().isPresent()) { - account.setBalance(override.getBalance().get()); - } - override.getCode().ifPresent(n -> account.setCode(Bytes.fromHexString(n))); + override.getBalance().ifPresent(account::setBalance); + override.getCode().ifPresent(code -> account.setCode(Bytes.fromHexString(code))); + override + .getState() + .ifPresent( + d -> { + account.clearStorage(); + d.forEach( + (key, value) -> + account.setStorageValue( + UInt256.fromHexString(key), UInt256.fromHexString(value))); + }); override .getStateDiff() .ifPresent( diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/BlockSimulatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/BlockSimulatorTest.java index b60a4afb6e5..567fb56cfa0 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/BlockSimulatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/BlockSimulatorTest.java @@ -30,6 +30,7 @@ import org.hyperledger.besu.datatypes.StateOverride; import org.hyperledger.besu.datatypes.StateOverrideMap; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.datatypes.parameters.UnsignedLongParameter; import org.hyperledger.besu.ethereum.GasLimitCalculator; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; @@ -50,11 +51,9 @@ import java.math.BigInteger; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; @@ -168,24 +167,25 @@ public void shouldStopWhenTransactionSimulationIsEmpty() { @Test public void shouldApplyStateOverridesCorrectly() { - StateOverrideMap stateOverrideMap = mock(StateOverrideMap.class); + StateOverrideMap stateOverrideMap = new StateOverrideMap(); Address address = mock(Address.class); - StateOverride stateOverride = mock(StateOverride.class); - MutableAccount mutableAccount = mock(MutableAccount.class); + StateOverride stateOverride = + new StateOverride.Builder() + .withBalance(Wei.of(456L)) + .withNonce(new UnsignedLongParameter(123L)) + .withCode("") + .withStateDiff(Map.of("0x0", "0x1")) + .build(); - when(stateOverrideMap.keySet()).thenReturn(Set.of(address)); - when(stateOverrideMap.get(address)).thenReturn(stateOverride); + stateOverrideMap.put(address, stateOverride); WorldUpdater worldUpdater = mock(WorldUpdater.class); when(mutableWorldState.updater()).thenReturn(worldUpdater); + MutableAccount mutableAccount = mock(MutableAccount.class); + when(mutableAccount.getAddress()).thenReturn(address); when(worldUpdater.getOrCreate(address)).thenReturn(mutableAccount); - when(stateOverride.getNonce()).thenReturn(Optional.of(123L)); - when(stateOverride.getBalance()).thenReturn(Optional.of(Wei.of(456L))); - when(stateOverride.getCode()).thenReturn(Optional.of("")); - when(stateOverride.getStateDiff()).thenReturn(Optional.of(new HashMap<>(Map.of("0x0", "0x1")))); - blockSimulator.applyStateOverrides(stateOverrideMap, mutableWorldState); verify(mutableAccount).setNonce(anyLong()); 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 577bf82d94d..3515a5b6719 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 @@ -121,7 +121,7 @@ public void testOverrides_whenNoOverrides_noUpdates() { when(mutableAccount.getAddress()).thenReturn(DEFAULT_FROM); // called from logging StateOverride.Builder builder = new StateOverride.Builder(); StateOverride override = builder.build(); - transactionSimulator.applyOverrides(mutableAccount, override); + TransactionSimulator.applyOverrides(mutableAccount, override); verify(mutableAccount).getAddress(); verifyNoMoreInteractions(mutableAccount); } @@ -132,7 +132,7 @@ public void testOverrides_whenBalanceOverrides_balanceIsUpdated() { when(mutableAccount.getAddress()).thenReturn(DEFAULT_FROM); StateOverride.Builder builder = new StateOverride.Builder().withBalance(Wei.of(99)); StateOverride override = builder.build(); - transactionSimulator.applyOverrides(mutableAccount, override); + TransactionSimulator.applyOverrides(mutableAccount, override); verify(mutableAccount).setBalance(eq(Wei.of(99))); } @@ -145,7 +145,25 @@ public void testOverrides_whenStateDiffOverrides_stateIsUpdated() { StateOverride.Builder builder = new StateOverride.Builder().withStateDiff(Map.of(storageKey, storageValue)); StateOverride override = builder.build(); - transactionSimulator.applyOverrides(mutableAccount, override); + TransactionSimulator.applyOverrides(mutableAccount, override); + verify(mutableAccount) + .setStorageValue( + eq(UInt256.fromHexString(storageKey)), eq(UInt256.fromHexString(storageValue))); + } + + @Test + public void testOverrides_whenStateOverrides_stateIsUpdated() { + MutableAccount mutableAccount = mock(MutableAccount.class); + when(mutableAccount.getAddress()).thenReturn(DEFAULT_FROM); + final String storageKey = "0x01a2"; + final String storageValue = "0x00ff"; + StateOverride.Builder builder = + new StateOverride.Builder().withState(Map.of(storageKey, storageValue)); + StateOverride override = builder.build(); + TransactionSimulator.applyOverrides(mutableAccount, override); + + verify(mutableAccount).clearStorage(); + verify(mutableAccount) .setStorageValue( eq(UInt256.fromHexString(storageKey)), eq(UInt256.fromHexString(storageValue))); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/util/StateOverrideParameterTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/util/StateOverrideParameterTest.java index 1f095a1b1bc..6fb452d9afe 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/util/StateOverrideParameterTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/util/StateOverrideParameterTest.java @@ -16,6 +16,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.hyperledger.besu.datatypes.Address; @@ -25,6 +26,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import java.util.Map; import java.util.Optional; import com.fasterxml.jackson.databind.ObjectMapper; @@ -63,6 +65,7 @@ public void jsonDeserializesCorrectly() throws Exception { assertThat(stateOverride.getNonce().get()).isEqualTo(158); assertThat(stateOverride.getBalance()).isEqualTo(Optional.of(Wei.of(1))); + assertFalse(stateOverride.getState().isPresent()); assertFalse(stateOverride.getStateDiff().isPresent()); } @@ -91,6 +94,7 @@ public void jsonWithCodeDeserializesCorrectly() throws Exception { assertFalse(stateOverride.getNonce().isPresent()); assertThat(stateOverride.getBalance()).isEqualTo(Optional.of(Wei.of(1))); assertThat(stateOverride.getCode()).isEqualTo(Optional.of(CODE_STRING)); + assertFalse(stateOverride.getState().isPresent()); assertFalse(stateOverride.getStateDiff().isPresent()); } @@ -118,6 +122,7 @@ public void jsonWithHexNonceDeserializesCorrectly() throws Exception { assertThat(stateOverride.getBalance()).isEqualTo(Optional.of(Wei.of(1))); assertThat(stateOverride.getNonce().get()).isEqualTo(158); // 0x9e + assertFalse(stateOverride.getState().isPresent()); assertFalse(stateOverride.getStateDiff().isPresent()); } @@ -133,7 +138,7 @@ public void jsonWithStorageOverridesDeserializesCorrectly() throws Exception { + "{" + "\"balance\": \"0x01\"," + "\"nonce\": \"0x9E\"," - + "\"stateDiff\": {" + + "\"state\": {" + "\"" + STORAGE_KEY + "\": \"" @@ -150,8 +155,9 @@ public void jsonWithStorageOverridesDeserializesCorrectly() throws Exception { final StateOverride stateOverride = stateOverrideParam.get(Address.fromHexString(ADDRESS_HEX1)); assertThat(stateOverride.getNonce().get()).isEqualTo(158); - assertTrue(stateOverride.getStateDiff().isPresent()); - assertThat(stateOverride.getStateDiff().get().get(STORAGE_KEY)).isEqualTo(STORAGE_VALUE); + assertTrue(stateOverride.getState().isPresent()); + assertThat(stateOverride.getState().get().get(STORAGE_KEY)).isEqualTo(STORAGE_VALUE); + assertFalse(stateOverride.getStateDiff().isPresent()); } @Test @@ -166,7 +172,7 @@ public void jsonWithMultipleStateOverridesDeserializesCorrectly() throws Excepti + "{" + "\"balance\": \"0x01\"," + "\"nonce\": \"0x9E\"," - + "\"stateDiff\": {" + + "\"state\": {" + "\"" + STORAGE_KEY + "\": \"" @@ -179,7 +185,7 @@ public void jsonWithMultipleStateOverridesDeserializesCorrectly() throws Excepti + "{" + "\"balance\": \"0xFF\"," + "\"nonce\": \"0x9D\"," - + "\"stateDiff\": {" + + "\"state\": {" + "\"" + STORAGE_KEY + "\": \"" @@ -197,18 +203,35 @@ public void jsonWithMultipleStateOverridesDeserializesCorrectly() throws Excepti stateOverrideParam.get(Address.fromHexString(ADDRESS_HEX1)); assertThat(stateOverride1.getNonce().get()).isEqualTo(158); assertThat(stateOverride1.getBalance()).isEqualTo(Optional.of(Wei.fromHexString("0x01"))); - assertTrue(stateOverride1.getStateDiff().isPresent()); - assertThat(stateOverride1.getStateDiff().get().get(STORAGE_KEY)).isEqualTo(STORAGE_VALUE); + assertTrue(stateOverride1.getState().isPresent()); + assertThat(stateOverride1.getState().get().get(STORAGE_KEY)).isEqualTo(STORAGE_VALUE); + assertFalse(stateOverride1.getStateDiff().isPresent()); final StateOverride stateOverride2 = stateOverrideParam.get(Address.fromHexString(ADDRESS_HEX2)); assertThat(stateOverride2.getNonce().get()).isEqualTo(157); assertThat(stateOverride2.getBalance()).isEqualTo(Optional.of(Wei.fromHexString("0xFF"))); - assertTrue(stateOverride2.getStateDiff().isPresent()); - assertThat(stateOverride2.getStateDiff().get().get(STORAGE_KEY)).isEqualTo(STORAGE_VALUE); + assertTrue(stateOverride2.getState().isPresent()); + assertThat(stateOverride2.getState().get().get(STORAGE_KEY)).isEqualTo(STORAGE_VALUE); + assertFalse(stateOverride2.getStateDiff().isPresent()); } private JsonRpcRequest readJsonAsJsonRpcRequest(final String json) throws java.io.IOException { return new ObjectMapper().readValue(json, JsonRpcRequest.class); } + + @Test + public void shouldThrowExceptionWhenStateAndStateDiffAreBothPresent() { + Exception exception = + assertThrows( + IllegalStateException.class, + () -> + new StateOverride.Builder() + .withState(Map.of("0x1234", "0x5678")) + .withStateDiff(Map.of("0x1234", "0x5678")) + .build()); + + final String expectedMessage = "Cannot set both state and stateDiff"; + assertThat(exception.getMessage()).isEqualTo(expectedMessage); + } }