Simulation: Add state to StateOverride (eth_call, eth_simulateV1). (#8166)

Signed-off-by: Gabriel-Trintinalia <gabriel.trintinalia@consensys.net>
This commit is contained in:
Gabriel-Trintinalia
2025-02-03 07:50:47 +08:00
committed by GitHub
parent 6d51821614
commit 8d360a3d0e
7 changed files with 132 additions and 45 deletions

View File

@@ -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<Wei> balance;
private final Optional<Long> nonce;
private final Optional<String> code;
private final Optional<Map<String, String>> state;
private final Optional<Map<String, String>> stateDiff;
private final Optional<Address> movePrecompileToAddress;
@@ -42,11 +45,13 @@ public class StateOverride {
final Optional<Wei> balance,
final Optional<Long> nonce,
final Optional<String> code,
final Optional<Map<String, String>> state,
final Optional<Map<String, String>> stateDiff,
final Optional<Address> movePrecompileToAddress) {
this.balance = balance;
this.nonce = nonce;
this.code = code;
this.state = state;
this.stateDiff = stateDiff;
this.movePrecompileToAddress = movePrecompileToAddress;
}
@@ -83,6 +88,15 @@ public class StateOverride {
*
* @return the state override map if present
*/
public Optional<Map<String, String>> getState() {
return state;
}
/**
* Gets the state diff override map
*
* @return the state diff override map if present
*/
public Optional<Map<String, String>> getStateDiff() {
return stateDiff;
}
@@ -102,6 +116,7 @@ public class StateOverride {
private Optional<Wei> balance = Optional.empty();
private Optional<Long> nonce = Optional.empty();
private Optional<String> code = Optional.empty();
private Optional<Map<String, String>> state = Optional.empty();
private Optional<Map<String, String>> stateDiff = Optional.empty();
private Optional<Address> movePrecompileToAddress = Optional.empty();
@@ -141,6 +156,17 @@ public class StateOverride {
return this;
}
/**
* Sets the state override
*
* @param state the map of state overrides
* @return the builder
*/
public Builder withState(final Map<String, String> state) {
this.state = Optional.ofNullable(state);
return this;
}
/**
* Sets the state diff override
*
@@ -169,7 +195,8 @@ public class StateOverride {
* @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 class StateOverride {
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 class StateOverride {
+ nonce
+ ", code="
+ code
+ ", state="
+ state
+ ", stateDiff="
+ stateDiff
+ ", movePrecompileToAddress="

View File

@@ -124,6 +124,27 @@ public class EthCallTest {
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<StateOverrideMap> 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();

View File

@@ -54,7 +54,6 @@ import java.util.Optional;
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 @@ public class BlockSimulator {
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();
}

View File

@@ -460,13 +460,21 @@ public class TransactionSimulator {
}
@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(

View File

@@ -30,6 +30,7 @@ import org.hyperledger.besu.datatypes.Hash;
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 org.hyperledger.besu.plugin.data.BlockOverrides;
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 class BlockSimulatorTest {
@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());

View File

@@ -121,7 +121,7 @@ public class TransactionSimulatorTest {
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 class TransactionSimulatorTest {
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 class TransactionSimulatorTest {
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)));

View File

@@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.util;
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.datatypes.Wei;
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 class StateOverrideParameterTest {
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 class StateOverrideParameterTest {
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 class StateOverrideParameterTest {
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 class StateOverrideParameterTest {
+ "{"
+ "\"balance\": \"0x01\","
+ "\"nonce\": \"0x9E\","
+ "\"stateDiff\": {"
+ "\"state\": {"
+ "\""
+ STORAGE_KEY
+ "\": \""
@@ -150,8 +155,9 @@ public class StateOverrideParameterTest {
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 class StateOverrideParameterTest {
+ "{"
+ "\"balance\": \"0x01\","
+ "\"nonce\": \"0x9E\","
+ "\"stateDiff\": {"
+ "\"state\": {"
+ "\""
+ STORAGE_KEY
+ "\": \""
@@ -179,7 +185,7 @@ public class StateOverrideParameterTest {
+ "{"
+ "\"balance\": \"0xFF\","
+ "\"nonce\": \"0x9D\","
+ "\"stateDiff\": {"
+ "\"state\": {"
+ "\""
+ STORAGE_KEY
+ "\": \""
@@ -197,18 +203,35 @@ public class StateOverrideParameterTest {
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);
}
}