add optional worldstate move to debug_setHead

Signed-off-by: garyschulte <garyschulte@gmail.com>
garyschulte 1 day ago
parent f855d5b72f
commit 09f55b8a56
  1. 46
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugSetHead.java
  2. 164
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugSetHeadTest.java

@ -21,16 +21,17 @@ import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameterOrBlockHash;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import java.util.Optional;
public class DebugSetHead extends AbstractBlockParameterMethod {
public class DebugSetHead extends AbstractBlockParameterOrBlockHashMethod {
private final ProtocolContext protocolContext;
public DebugSetHead(final BlockchainQueries blockchain, final ProtocolContext protocolContext) {
@ -45,26 +46,51 @@ public class DebugSetHead extends AbstractBlockParameterMethod {
}
@Override
protected BlockParameter blockParameter(final JsonRpcRequestContext request) {
protected BlockParameterOrBlockHash blockParameterOrBlockHash(
final JsonRpcRequestContext requestContext) {
try {
return request.getRequiredParameter(0, BlockParameter.class);
return requestContext.getRequiredParameter(0, BlockParameterOrBlockHash.class);
} catch (JsonRpcParameterException e) {
throw new InvalidJsonRpcParameters(
"Invalid block parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e);
"Invalid block or block hash parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e);
}
}
@Override
protected Object resultByBlockNumber(
final JsonRpcRequestContext request, final long blockNumber) {
final Optional<Hash> maybeBlockHash = getBlockchainQueries().getBlockHashByNumber(blockNumber);
protected Object resultByBlockHash(final JsonRpcRequestContext request, final Hash blockHash) {
var blockchainQueries = getBlockchainQueries();
Optional<BlockHeader> maybeBlockHeader = blockchainQueries.getBlockHeaderByHash(blockHash);
Optional<Boolean> maybeMoveWorldstate = shouldMoveWorldstate(request);
if (maybeBlockHash.isEmpty()) {
if (maybeBlockHeader.isEmpty()) {
return new JsonRpcErrorResponse(request.getRequest().getId(), UNKNOWN_BLOCK);
}
protocolContext.getBlockchain().rewindToBlock(maybeBlockHash.get());
protocolContext.getBlockchain().rewindToBlock(maybeBlockHeader.get().getBlockHash());
// Optionally move the worldstate to the specified blockhash, if it is present
if (maybeMoveWorldstate.orElse(Boolean.FALSE)) {
var blockHeader = maybeBlockHeader.get();
var archive = blockchainQueries.getWorldStateArchive();
if (archive.isWorldStateAvailable(blockHeader.getStateRoot(), blockHeader.getBlockHash())) {
// WARNING, this can be dangerous for a DiffBasedWorldstate if a concurrent
// process attempts to move or modify the head worldstate.
// for Foreset worldstates, this is essentially a no-op.
archive.getMutable(blockHeader.getStateRoot(), blockHeader.getBlockHash());
}
}
return JsonRpcSuccessResponse.SUCCESS_RESULT;
}
private Optional<Boolean> shouldMoveWorldstate(final JsonRpcRequestContext request) {
try {
return request.getOptionalParameter(1, Boolean.class);
} catch (JsonRpcParameterException e) {
throw new InvalidJsonRpcParameters(
"Invalid should move worldstate boolean parameter (index 1)",
RpcErrorType.INVALID_PARAMS,
e);
}
}
}

@ -0,0 +1,164 @@
/*
* Copyright contributors to Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import org.hyperledger.besu.crypto.Hash;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.AbstractJsonRpcHttpServiceTest;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameterOrBlockHash;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.MiningParameters;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.plugin.services.rpc.RpcResponseType;
import java.util.Optional;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.apache.tuweni.bytes.Bytes;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
/**
* This test only exercises bonsai worldstate since forest is essentially a no-op for moving the
* worldstate.
*/
public class DebugSetHeadTest extends AbstractJsonRpcHttpServiceTest {
DebugSetHead debugSetHead;
Blockchain blockchain;
WorldStateArchive archive;
ProtocolContext protocolContext;
ProtocolSchedule protocolSchedule;
@Override
@BeforeEach
public void setup() throws Exception {
setupBonsaiBlockchain();
blockchain = blockchainSetupUtil.getBlockchain();
protocolContext = blockchainSetupUtil.getProtocolContext();
protocolSchedule = blockchainSetupUtil.getProtocolSchedule();
;
archive = blockchainSetupUtil.getWorldArchive();
debugSetHead =
new DebugSetHead(
new BlockchainQueries(
protocolSchedule, blockchain, archive, MiningParameters.MINING_DISABLED),
protocolContext);
startService();
}
@ParameterizedTest
@ValueSource(
strings = {
"0x01",
"0x02",
"0x3d813a0ffc9cd04436e17e3e9c309f1e80df0407078e50355ce0d570b5424812",
"0x4e9a67b663f9abe03e7e9fd5452c9497998337077122f44ee78a466f6a7358de"
})
public void assertOnlyChainHeadMoves(final String blockParam) {
var chainTip = blockchain.getChainHead().getBlockHeader();
var blockOne = getBlockHeaderForHashOrNumber(blockParam).orElse(null);
assertThat(blockOne).isNotNull();
assertThat(blockOne).isNotEqualTo(chainTip);
// move the head to param val, number or hash
debugSetHead.response(debugSetHead(blockParam, false));
// get the new chainTip:
var newChainTip = blockchain.getChainHead().getBlockHeader();
// assert the chain moved, and the worldstate did not
assertThat(newChainTip).isEqualTo(blockOne);
assertThat(archive.getMutable().rootHash()).isEqualTo(chainTip.getStateRoot());
}
@ParameterizedTest
@ValueSource(
strings = {
"0x01",
"0x02",
"0x3d813a0ffc9cd04436e17e3e9c309f1e80df0407078e50355ce0d570b5424812",
"0x4e9a67b663f9abe03e7e9fd5452c9497998337077122f44ee78a466f6a7358de"
})
public void assertBothChainHeadAndWorldStatByNumber(final String blockParam) {
var chainTip = blockchain.getChainHead().getBlockHeader();
var blockOne = getBlockHeaderForHashOrNumber(blockParam).orElse(null);
assertThat(blockOne).isNotNull();
assertThat(blockOne).isNotEqualTo(chainTip);
// move the head and worldstate to param val number or hash
debugSetHead.response(debugSetHead(blockParam, true));
// get the new chainTip:
var newChainTip = blockchain.getChainHead().getBlockHeader();
// assert both the chain and worldstate moved to block one
assertThat(newChainTip).isEqualTo(blockOne);
assertThat(archive.getMutable().rootHash()).isEqualTo(blockOne.getStateRoot());
}
@Test
public void assertNotFound() {
var chainTip = blockchain.getChainHead().getBlockHeader();
// move the head to number just after chain head
var resp = debugSetHead.response(debugSetHead("" + chainTip.getNumber() + 1, false));
assertThat(resp.getType()).isEqualTo(RpcResponseType.ERROR);
// move the head to some arbitrary hash
var resp2 =
debugSetHead.response(
debugSetHead(Hash.keccak256(Bytes.fromHexString("0xdeadbeef")).toHexString(), false));
assertThat(resp2.getType()).isEqualTo(RpcResponseType.ERROR);
// get the new chainTip:
var newChainTip = blockchain.getChainHead().getBlockHeader();
// assert neither the chain nor the worldstate moved
assertThat(newChainTip).isEqualTo(chainTip);
assertThat(archive.getMutable().rootHash()).isEqualTo(chainTip.getStateRoot());
}
private JsonRpcRequestContext debugSetHead(
final String numberOrHash, final Boolean moveWorldState) {
return new JsonRpcRequestContext(
new JsonRpcRequest("2.0", "debug_setHead", new Object[] {numberOrHash, moveWorldState}));
}
private Optional<BlockHeader> getBlockHeaderForHashOrNumber(final String input) {
try {
var param = new BlockParameterOrBlockHash(input);
if (param.getHash().isPresent()) {
return blockchain.getBlockHeader(param.getHash().get());
} else if (param.getNumber().isPresent()) {
return blockchain.getBlockHeader(param.getNumber().getAsLong());
}
} catch (JsonProcessingException ignored) {
// meh
}
return Optional.empty();
}
}
Loading…
Cancel
Save