mirror of https://github.com/hyperledger/besu
feature: Add optional worldstate move flag to debug_setHead (#7821)
* add optional worldstate move to debug_setHead * make state rolling occur incrementally so as not to overwhelm memory and resources Signed-off-by: garyschulte <garyschulte@gmail.com>pull/7860/head
parent
1da0e9f232
commit
d415b7db53
@ -0,0 +1,198 @@ |
|||||||
|
/* |
||||||
|
* 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 java.lang.Boolean.FALSE; |
||||||
|
import static java.lang.Boolean.TRUE; |
||||||
|
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.MiningConfiguration; |
||||||
|
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, MiningConfiguration.MINING_DISABLED), |
||||||
|
protocolContext, |
||||||
|
// a value of 2 here exercises all the state rolling code paths
|
||||||
|
2); |
||||||
|
startService(); |
||||||
|
} |
||||||
|
|
||||||
|
@ParameterizedTest |
||||||
|
@ValueSource( |
||||||
|
strings = {"0x01", "0x4e9a67b663f9abe03e7e9fd5452c9497998337077122f44ee78a466f6a7358de"}) |
||||||
|
public void assertOnlyChainHeadMovesWorldParameterAbsent(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, Optional.empty())); |
||||||
|
|
||||||
|
// 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 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, Optional.of(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, Optional.of(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, Optional.of(TRUE))); |
||||||
|
assertThat(resp.getType()).isEqualTo(RpcResponseType.ERROR); |
||||||
|
|
||||||
|
// move the head to some arbitrary hash
|
||||||
|
var resp2 = |
||||||
|
debugSetHead.response( |
||||||
|
debugSetHead( |
||||||
|
Hash.keccak256(Bytes.fromHexString("0xdeadbeef")).toHexString(), |
||||||
|
Optional.of(TRUE))); |
||||||
|
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 Optional<Boolean> moveWorldState) { |
||||||
|
if (moveWorldState.isPresent()) { |
||||||
|
return new JsonRpcRequestContext( |
||||||
|
new JsonRpcRequest( |
||||||
|
"2.0", "debug_setHead", new Object[] {numberOrHash, moveWorldState.get()})); |
||||||
|
} else { |
||||||
|
return new JsonRpcRequestContext( |
||||||
|
new JsonRpcRequest("2.0", "debug_setHead", new Object[] {numberOrHash})); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
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…
Reference in new issue