mirror of https://github.com/hyperledger/besu
[PAN-2946] Retesteth support (#1818)
Add support for retesteth, a JSON-RPC based reference testing API. Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>pull/2/head
parent
bb9c6538dc
commit
06a284817e
@ -0,0 +1,53 @@ |
||||
/* |
||||
* Copyright 2019 ConsenSys AG. |
||||
* |
||||
* 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. |
||||
*/ |
||||
|
||||
apply plugin: 'java-library' |
||||
|
||||
jar { |
||||
baseName 'pantheon-retesteth' |
||||
manifest { |
||||
attributes( |
||||
'Specification-Title': baseName, |
||||
'Specification-Version': project.version, |
||||
'Implementation-Title': baseName, |
||||
'Implementation-Version': calculateVersion() |
||||
) |
||||
} |
||||
} |
||||
|
||||
dependencies { |
||||
implementation(project(':config')) |
||||
implementation(project(':ethereum:blockcreation')) |
||||
implementation(project(':ethereum:core')) |
||||
implementation(project(':ethereum:eth')) |
||||
implementation(project(':ethereum:graphql')) |
||||
implementation(project(':ethereum:jsonrpc')) |
||||
implementation(project(':ethereum:rlp')) |
||||
implementation(project(':ethereum:p2p')) |
||||
// implementation(project(':pantheon')) |
||||
implementation(project(':metrics:core')) |
||||
implementation(project(':nat')) |
||||
implementation(project(':services:kvstore')) |
||||
implementation(project(':util')) |
||||
|
||||
implementation 'com.google.guava:guava' |
||||
implementation 'io.vertx:vertx-core' |
||||
implementation 'io.vertx:vertx-web' |
||||
implementation 'org.apache.logging.log4j:log4j-api' |
||||
|
||||
testImplementation 'junit:junit' |
||||
testImplementation 'org.assertj:assertj-core' |
||||
testImplementation 'org.mockito:mockito-core' |
||||
|
||||
runtime 'org.apache.logging.log4j:log4j-core' |
||||
} |
@ -0,0 +1,83 @@ |
||||
/* |
||||
* Copyright 2019 ConsenSys AG. |
||||
* |
||||
* 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. |
||||
*/ |
||||
package tech.pegasys.pantheon.ethereum.retesteth; |
||||
|
||||
import tech.pegasys.pantheon.ethereum.BlockValidator; |
||||
import tech.pegasys.pantheon.ethereum.MainnetBlockValidator; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockImporter; |
||||
import tech.pegasys.pantheon.ethereum.core.TransactionFilter; |
||||
import tech.pegasys.pantheon.ethereum.core.Wei; |
||||
import tech.pegasys.pantheon.ethereum.mainnet.BlockProcessor; |
||||
import tech.pegasys.pantheon.ethereum.mainnet.MainnetBlockImporter; |
||||
import tech.pegasys.pantheon.ethereum.mainnet.MainnetBlockProcessor; |
||||
import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; |
||||
import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSpec; |
||||
|
||||
import java.math.BigInteger; |
||||
import java.util.Optional; |
||||
|
||||
public class NoRewardProtocolScheduleWrapper<C> implements ProtocolSchedule<C> { |
||||
|
||||
private final ProtocolSchedule<C> delegate; |
||||
|
||||
NoRewardProtocolScheduleWrapper(final ProtocolSchedule<C> delegate) { |
||||
this.delegate = delegate; |
||||
} |
||||
|
||||
@Override |
||||
public ProtocolSpec<C> getByBlockNumber(final long number) { |
||||
final ProtocolSpec<C> original = delegate.getByBlockNumber(number); |
||||
final BlockProcessor noRewardBlockProcessor = |
||||
new MainnetBlockProcessor( |
||||
original.getTransactionProcessor(), |
||||
original.getTransactionReceiptFactory(), |
||||
Wei.ZERO, |
||||
original.getMiningBeneficiaryCalculator(), |
||||
original.isSkipZeroBlockRewards()); |
||||
final BlockValidator<C> noRewardBlockValidator = |
||||
new MainnetBlockValidator<>( |
||||
original.getBlockHeaderValidator(), |
||||
original.getBlockBodyValidator(), |
||||
noRewardBlockProcessor); |
||||
final BlockImporter<C> noRewardBlockImporter = |
||||
new MainnetBlockImporter<>(noRewardBlockValidator); |
||||
return new ProtocolSpec<>( |
||||
original.getName(), |
||||
original.getEvm(), |
||||
original.getTransactionValidator(), |
||||
original.getTransactionProcessor(), |
||||
original.getBlockHeaderValidator(), |
||||
original.getOmmerHeaderValidator(), |
||||
original.getBlockBodyValidator(), |
||||
noRewardBlockProcessor, |
||||
noRewardBlockImporter, |
||||
noRewardBlockValidator, |
||||
original.getBlockHeaderFunctions(), |
||||
original.getTransactionReceiptFactory(), |
||||
original.getDifficultyCalculator(), |
||||
Wei.ZERO, // block reward
|
||||
original.getMiningBeneficiaryCalculator(), |
||||
original.getPrecompileContractRegistry(), |
||||
original.isSkipZeroBlockRewards()); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<BigInteger> getChainId() { |
||||
return delegate.getChainId(); |
||||
} |
||||
|
||||
@Override |
||||
public void setTransactionFilter(final TransactionFilter transactionFilter) { |
||||
delegate.setTransactionFilter(transactionFilter); |
||||
} |
||||
} |
@ -0,0 +1,58 @@ |
||||
/* |
||||
* Copyright 2019 ConsenSys AG. |
||||
* |
||||
* 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. |
||||
*/ |
||||
package tech.pegasys.pantheon.ethereum.retesteth; |
||||
|
||||
import java.time.Clock; |
||||
import java.time.Instant; |
||||
import java.time.ZoneId; |
||||
import java.util.Optional; |
||||
|
||||
public class RetestethClock extends Clock { |
||||
|
||||
private Optional<Instant> fixedInstant; |
||||
private final Clock delegateClock; |
||||
|
||||
RetestethClock() { |
||||
this(Clock.systemUTC()); |
||||
} |
||||
|
||||
private RetestethClock(final Clock delegateClock) { |
||||
fixedInstant = Optional.empty(); |
||||
this.delegateClock = delegateClock; |
||||
} |
||||
|
||||
@Override |
||||
public ZoneId getZone() { |
||||
return delegateClock.getZone(); |
||||
} |
||||
|
||||
@Override |
||||
public Clock withZone(final ZoneId zone) { |
||||
final RetestethClock zonedClock = new RetestethClock(delegateClock.withZone(zone)); |
||||
zonedClock.fixedInstant = fixedInstant; |
||||
return zonedClock; |
||||
} |
||||
|
||||
@Override |
||||
public Instant instant() { |
||||
return fixedInstant.orElseGet(delegateClock::instant); |
||||
} |
||||
|
||||
public void resetTime(final long time) { |
||||
fixedInstant = Optional.of(Instant.ofEpochSecond(time)); |
||||
} |
||||
|
||||
public void advanceSeconds(final long seconds) { |
||||
fixedInstant = Optional.of(Instant.ofEpochSecond(instant().getEpochSecond() + seconds)); |
||||
} |
||||
} |
@ -0,0 +1,28 @@ |
||||
/* |
||||
* Copyright 2019 ConsenSys AG. |
||||
* |
||||
* 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. |
||||
*/ |
||||
package tech.pegasys.pantheon.ethereum.retesteth; |
||||
|
||||
import java.nio.file.Path; |
||||
|
||||
public class RetestethConfiguration { |
||||
|
||||
private final Path dataPath; |
||||
|
||||
public RetestethConfiguration(final Path dataPath) { |
||||
this.dataPath = dataPath; |
||||
} |
||||
|
||||
Path getDataPath() { |
||||
return dataPath; |
||||
} |
||||
} |
@ -0,0 +1,252 @@ |
||||
/* |
||||
* Copyright 2019 ConsenSys AG. |
||||
* |
||||
* 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. |
||||
*/ |
||||
package tech.pegasys.pantheon.ethereum.retesteth; |
||||
|
||||
import static tech.pegasys.pantheon.config.JsonUtil.normalizeKeys; |
||||
|
||||
import tech.pegasys.pantheon.config.JsonGenesisConfigOptions; |
||||
import tech.pegasys.pantheon.config.JsonUtil; |
||||
import tech.pegasys.pantheon.ethereum.ProtocolContext; |
||||
import tech.pegasys.pantheon.ethereum.blockcreation.IncrementingNonceGenerator; |
||||
import tech.pegasys.pantheon.ethereum.chain.DefaultMutableBlockchain; |
||||
import tech.pegasys.pantheon.ethereum.chain.GenesisState; |
||||
import tech.pegasys.pantheon.ethereum.core.Address; |
||||
import tech.pegasys.pantheon.ethereum.core.Block; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockHeader; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockHeaderFunctions; |
||||
import tech.pegasys.pantheon.ethereum.core.MutableWorldState; |
||||
import tech.pegasys.pantheon.ethereum.core.Wei; |
||||
import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; |
||||
import tech.pegasys.pantheon.ethereum.eth.manager.EthMessages; |
||||
import tech.pegasys.pantheon.ethereum.eth.manager.EthPeers; |
||||
import tech.pegasys.pantheon.ethereum.eth.manager.EthScheduler; |
||||
import tech.pegasys.pantheon.ethereum.eth.sync.state.SyncState; |
||||
import tech.pegasys.pantheon.ethereum.eth.transactions.PendingTransactions; |
||||
import tech.pegasys.pantheon.ethereum.eth.transactions.TransactionPool; |
||||
import tech.pegasys.pantheon.ethereum.eth.transactions.TransactionPoolConfiguration; |
||||
import tech.pegasys.pantheon.ethereum.eth.transactions.TransactionPoolFactory; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.processor.BlockReplay; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.BlockchainQueries; |
||||
import tech.pegasys.pantheon.ethereum.mainnet.EthHashSolver; |
||||
import tech.pegasys.pantheon.ethereum.mainnet.EthHasher; |
||||
import tech.pegasys.pantheon.ethereum.mainnet.HeaderValidationMode; |
||||
import tech.pegasys.pantheon.ethereum.mainnet.MainnetBlockHeaderFunctions; |
||||
import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule; |
||||
import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; |
||||
import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSpec; |
||||
import tech.pegasys.pantheon.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; |
||||
import tech.pegasys.pantheon.ethereum.storage.keyvalue.WorldStateKeyValueStorage; |
||||
import tech.pegasys.pantheon.ethereum.storage.keyvalue.WorldStatePreimageKeyValueStorage; |
||||
import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; |
||||
import tech.pegasys.pantheon.metrics.MetricsSystem; |
||||
import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; |
||||
import tech.pegasys.pantheon.services.kvstore.InMemoryKeyValueStorage; |
||||
|
||||
import java.util.Optional; |
||||
import java.util.concurrent.locks.ReentrantLock; |
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode; |
||||
import org.apache.logging.log4j.LogManager; |
||||
import org.apache.logging.log4j.Logger; |
||||
|
||||
public class RetestethContext { |
||||
|
||||
private static final Logger LOG = LogManager.getLogger(); |
||||
private static final EthHasher NO_WORK_HASHER = |
||||
(final byte[] buffer, final long nonce, final long number, final byte[] headerHash) -> {}; |
||||
|
||||
private final ReentrantLock contextLock = new ReentrantLock(); |
||||
private Address coinbase; |
||||
private DefaultMutableBlockchain blockchain; |
||||
private ProtocolContext<Void> protocolContext; |
||||
private BlockchainQueries blockchainQueries; |
||||
private ProtocolSchedule<Void> protocolSchedule; |
||||
private HeaderValidationMode headerValidationMode; |
||||
private BlockReplay blockReplay; |
||||
private RetestethClock retestethClock; |
||||
|
||||
private TransactionPool transactionPool; |
||||
private EthScheduler ethScheduler; |
||||
private EthHashSolver ethHashSolver; |
||||
|
||||
public boolean resetContext( |
||||
final String genesisConfigString, final String sealEngine, final Optional<Long> clockTime) { |
||||
contextLock.lock(); |
||||
try { |
||||
tearDownContext(); |
||||
return buildContext(genesisConfigString, sealEngine, clockTime); |
||||
} catch (final Exception e) { |
||||
LOG.error("Error shutting down existing runner", e); |
||||
return false; |
||||
} finally { |
||||
contextLock.unlock(); |
||||
} |
||||
} |
||||
|
||||
private void tearDownContext() { |
||||
try { |
||||
if (ethScheduler != null) { |
||||
ethScheduler.stop(); |
||||
ethScheduler.awaitStop(); |
||||
} |
||||
} catch (final InterruptedException e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
} |
||||
|
||||
private boolean buildContext( |
||||
final String genesisConfigString, final String sealEngine, final Optional<Long> clockTime) { |
||||
final ObjectNode genesisConfig = |
||||
normalizeKeys(JsonUtil.objectNodeFromString(genesisConfigString)); |
||||
|
||||
retestethClock = new RetestethClock(); |
||||
clockTime.ifPresent(retestethClock::resetTime); |
||||
final MetricsSystem metricsSystem = new NoOpMetricsSystem(); |
||||
|
||||
protocolSchedule = |
||||
MainnetProtocolSchedule.fromConfig( |
||||
JsonGenesisConfigOptions.fromJsonObject( |
||||
JsonUtil.getObjectNode(genesisConfig, "config").get())); |
||||
if ("NoReward".equalsIgnoreCase(sealEngine)) { |
||||
protocolSchedule = new NoRewardProtocolScheduleWrapper<>(protocolSchedule); |
||||
} |
||||
|
||||
final GenesisState genesisState = GenesisState.fromJson(genesisConfigString, protocolSchedule); |
||||
coinbase = genesisState.getBlock().getHeader().getCoinbase(); |
||||
|
||||
final WorldStateArchive worldStateArchive = |
||||
new WorldStateArchive( |
||||
new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()), |
||||
new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage())); |
||||
final MutableWorldState worldState = worldStateArchive.getMutable(); |
||||
genesisState.writeStateTo(worldState); |
||||
|
||||
blockchain = createInMemoryBlockchain(genesisState.getBlock()); |
||||
protocolContext = new ProtocolContext<>(blockchain, worldStateArchive, null); |
||||
|
||||
blockchainQueries = new BlockchainQueries(blockchain, worldStateArchive); |
||||
|
||||
final String sealengine = JsonUtil.getString(genesisConfig, "sealengine", ""); |
||||
headerValidationMode = |
||||
"NoProof".equals(sealengine) || "NoReward".equals(sealEngine) |
||||
? HeaderValidationMode.LIGHT |
||||
: HeaderValidationMode.FULL; |
||||
|
||||
final Iterable<Long> nonceGenerator = new IncrementingNonceGenerator(0); |
||||
ethHashSolver = |
||||
("NoProof".equals(sealengine) || "NoReward".equals(sealEngine)) |
||||
? new EthHashSolver(nonceGenerator, NO_WORK_HASHER) |
||||
: new EthHashSolver(nonceGenerator, new EthHasher.Light()); |
||||
|
||||
blockReplay = |
||||
new BlockReplay( |
||||
protocolSchedule, |
||||
blockchainQueries.getBlockchain(), |
||||
blockchainQueries.getWorldStateArchive()); |
||||
|
||||
// mining support
|
||||
|
||||
final EthPeers ethPeers = new EthPeers("reteseth", retestethClock, metricsSystem); |
||||
final SyncState syncState = new SyncState(blockchain, ethPeers); |
||||
|
||||
ethScheduler = new EthScheduler(1, 1, 1, metricsSystem); |
||||
final EthContext ethContext = new EthContext(ethPeers, new EthMessages(), ethScheduler); |
||||
|
||||
final TransactionPoolConfiguration transactionPoolConfiguration = |
||||
TransactionPoolConfiguration.builder().build(); |
||||
|
||||
transactionPool = |
||||
TransactionPoolFactory.createTransactionPool( |
||||
protocolSchedule, |
||||
protocolContext, |
||||
ethContext, |
||||
retestethClock, |
||||
metricsSystem, |
||||
syncState, |
||||
Wei.ZERO, |
||||
transactionPoolConfiguration); |
||||
|
||||
LOG.trace("Genesis Block {} ", genesisState::getBlock); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
private static DefaultMutableBlockchain createInMemoryBlockchain(final Block genesisBlock) { |
||||
return createInMemoryBlockchain(genesisBlock, new MainnetBlockHeaderFunctions()); |
||||
} |
||||
|
||||
private static DefaultMutableBlockchain createInMemoryBlockchain( |
||||
final Block genesisBlock, final BlockHeaderFunctions blockHeaderFunctions) { |
||||
final InMemoryKeyValueStorage keyValueStorage = new InMemoryKeyValueStorage(); |
||||
return new DefaultMutableBlockchain( |
||||
genesisBlock, |
||||
new KeyValueStoragePrefixedKeyBlockchainStorage(keyValueStorage, blockHeaderFunctions), |
||||
new NoOpMetricsSystem()); |
||||
} |
||||
|
||||
public ProtocolSchedule<Void> getProtocolSchedule() { |
||||
return protocolSchedule; |
||||
} |
||||
|
||||
public ProtocolContext<Void> getProtocolContext() { |
||||
return protocolContext; |
||||
} |
||||
|
||||
public long getBlockHeight() { |
||||
return blockchain.getChainHeadBlockNumber(); |
||||
} |
||||
|
||||
public ProtocolSpec<Void> getProtocolSpec(final long blockNumber) { |
||||
return getProtocolSchedule().getByBlockNumber(blockNumber); |
||||
} |
||||
|
||||
public BlockHeader getBlockHeader(final long blockNumber) { |
||||
return blockchain.getBlockHeader(blockNumber).get(); |
||||
} |
||||
|
||||
public BlockchainQueries getBlockchainQueries() { |
||||
return blockchainQueries; |
||||
} |
||||
|
||||
public HeaderValidationMode getHeaderValidationMode() { |
||||
return headerValidationMode; |
||||
} |
||||
|
||||
BlockReplay getBlockReplay() { |
||||
return blockReplay; |
||||
} |
||||
|
||||
public TransactionPool getTransactionPool() { |
||||
return transactionPool; |
||||
} |
||||
|
||||
PendingTransactions getPendingTransactions() { |
||||
return transactionPool.getPendingTransactions(); |
||||
} |
||||
|
||||
public Address getCoinbase() { |
||||
return coinbase; |
||||
} |
||||
|
||||
public DefaultMutableBlockchain getBlockchain() { |
||||
return blockchain; |
||||
} |
||||
|
||||
public RetestethClock getRetestethClock() { |
||||
return retestethClock; |
||||
} |
||||
|
||||
public EthHashSolver getEthHashSolver() { |
||||
return ethHashSolver; |
||||
} |
||||
} |
@ -0,0 +1,113 @@ |
||||
/* |
||||
* Copyright 2019 ConsenSys AG. |
||||
* |
||||
* 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. |
||||
*/ |
||||
package tech.pegasys.pantheon.ethereum.retesteth; |
||||
|
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcHttpService; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcMethodsFactory; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.health.HealthService; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.health.LivenessCheck; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.DebugAccountRange; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.DebugStorageRangeAt; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.EthBlockNumber; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.EthGetBalance; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.EthGetBlockByNumber; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.EthGetCode; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.EthGetTransactionCount; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.EthSendRawTransaction; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.JsonRpcMethod; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.Web3ClientVersion; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.results.BlockResultFactory; |
||||
import tech.pegasys.pantheon.ethereum.retesteth.methods.TestGetLogHash; |
||||
import tech.pegasys.pantheon.ethereum.retesteth.methods.TestImportRawBlock; |
||||
import tech.pegasys.pantheon.ethereum.retesteth.methods.TestMineBlocks; |
||||
import tech.pegasys.pantheon.ethereum.retesteth.methods.TestModifyTimestamp; |
||||
import tech.pegasys.pantheon.ethereum.retesteth.methods.TestRewindToBlock; |
||||
import tech.pegasys.pantheon.ethereum.retesteth.methods.TestSetChainParams; |
||||
import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; |
||||
|
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
import java.util.Optional; |
||||
|
||||
import io.vertx.core.Vertx; |
||||
|
||||
public class RetestethService { |
||||
|
||||
private final JsonRpcHttpService jsonRpcHttpService; |
||||
private final Vertx vertx; |
||||
|
||||
private final RetestethContext retestethContext; |
||||
|
||||
public RetestethService( |
||||
final String clientVersion, |
||||
final RetestethConfiguration retestethConfiguration, |
||||
final JsonRpcConfiguration jsonRpcConfiguration) { |
||||
vertx = Vertx.vertx(); |
||||
retestethContext = new RetestethContext(); |
||||
|
||||
final JsonRpcParameter parameters = new JsonRpcParameter(); |
||||
final BlockResultFactory blockResult = new BlockResultFactory(); |
||||
final Map<String, JsonRpcMethod> jsonRpcMethods = new HashMap<>(); |
||||
JsonRpcMethodsFactory.addMethods( |
||||
jsonRpcMethods, |
||||
new Web3ClientVersion(clientVersion), |
||||
new TestSetChainParams(retestethContext), |
||||
new TestImportRawBlock(retestethContext, parameters), |
||||
new EthBlockNumber(retestethContext::getBlockchainQueries, true), |
||||
new EthGetBlockByNumber( |
||||
retestethContext::getBlockchainQueries, blockResult, parameters, true), |
||||
new DebugAccountRange(parameters, retestethContext::getBlockchainQueries), |
||||
new EthGetBalance(retestethContext::getBlockchainQueries, parameters), |
||||
new EthGetCode(retestethContext::getBlockchainQueries, parameters), |
||||
new EthGetTransactionCount( |
||||
retestethContext::getBlockchainQueries, |
||||
retestethContext::getPendingTransactions, |
||||
parameters, |
||||
true), |
||||
new DebugStorageRangeAt( |
||||
parameters, |
||||
retestethContext::getBlockchainQueries, |
||||
retestethContext::getBlockReplay, |
||||
true), |
||||
new TestModifyTimestamp(retestethContext, parameters), |
||||
new EthSendRawTransaction(retestethContext::getTransactionPool, parameters, true), |
||||
new TestMineBlocks(retestethContext, parameters), |
||||
new TestGetLogHash(retestethContext, parameters), |
||||
new TestRewindToBlock(retestethContext, parameters)); |
||||
|
||||
jsonRpcHttpService = |
||||
new JsonRpcHttpService( |
||||
vertx, |
||||
retestethConfiguration.getDataPath(), |
||||
jsonRpcConfiguration, |
||||
new NoOpMetricsSystem(), |
||||
Optional.empty(), |
||||
jsonRpcMethods, |
||||
new HealthService(new LivenessCheck()), |
||||
HealthService.ALWAYS_HEALTHY); |
||||
} |
||||
|
||||
public void start() { |
||||
jsonRpcHttpService.start(); |
||||
} |
||||
|
||||
public void close() { |
||||
stop(); |
||||
} |
||||
|
||||
public void stop() { |
||||
jsonRpcHttpService.stop(); |
||||
} |
||||
} |
@ -0,0 +1,58 @@ |
||||
/* |
||||
* Copyright 2019 ConsenSys AG. |
||||
* |
||||
* 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. |
||||
*/ |
||||
package tech.pegasys.pantheon.ethereum.retesteth.methods; |
||||
|
||||
import tech.pegasys.pantheon.ethereum.core.Hash; |
||||
import tech.pegasys.pantheon.ethereum.core.LogSeries; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.JsonRpcMethod; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.TransactionReceiptWithMetadata; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; |
||||
import tech.pegasys.pantheon.ethereum.retesteth.RetestethContext; |
||||
import tech.pegasys.pantheon.ethereum.rlp.RLP; |
||||
|
||||
import java.util.Optional; |
||||
|
||||
public class TestGetLogHash implements JsonRpcMethod { |
||||
private final RetestethContext context; |
||||
private final JsonRpcParameter parameters; |
||||
|
||||
public TestGetLogHash(final RetestethContext context, final JsonRpcParameter parameters) { |
||||
this.context = context; |
||||
this.parameters = parameters; |
||||
} |
||||
|
||||
@Override |
||||
public String getName() { |
||||
return "test_getLogHash"; |
||||
} |
||||
|
||||
@Override |
||||
public JsonRpcResponse response(final JsonRpcRequest request) { |
||||
final Hash txHash = parameters.required(request.getParams(), 0, Hash.class); |
||||
|
||||
final Optional<TransactionReceiptWithMetadata> receipt = |
||||
context.getBlockchainQueries().transactionReceiptByTransactionHash(txHash); |
||||
return new JsonRpcSuccessResponse( |
||||
request.getId(), |
||||
receipt.map(this::calculateLogHash).orElse(Hash.EMPTY_LIST_HASH).toString()); |
||||
} |
||||
|
||||
private Hash calculateLogHash( |
||||
final TransactionReceiptWithMetadata transactionReceiptWithMetadata) { |
||||
final LogSeries logs = new LogSeries(transactionReceiptWithMetadata.getReceipt().getLogs()); |
||||
return Hash.hash(RLP.encode(logs::writeTo)); |
||||
} |
||||
} |
@ -0,0 +1,78 @@ |
||||
/* |
||||
* Copyright 2019 ConsenSys AG. |
||||
* |
||||
* 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. |
||||
*/ |
||||
package tech.pegasys.pantheon.ethereum.retesteth.methods; |
||||
|
||||
import tech.pegasys.pantheon.ethereum.ProtocolContext; |
||||
import tech.pegasys.pantheon.ethereum.core.Block; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockImporter; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.JsonRpcMethod; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; |
||||
import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSpec; |
||||
import tech.pegasys.pantheon.ethereum.retesteth.RetestethContext; |
||||
import tech.pegasys.pantheon.ethereum.rlp.RLP; |
||||
import tech.pegasys.pantheon.ethereum.rlp.RLPException; |
||||
import tech.pegasys.pantheon.util.bytes.BytesValue; |
||||
|
||||
import org.apache.logging.log4j.LogManager; |
||||
import org.apache.logging.log4j.Logger; |
||||
|
||||
public class TestImportRawBlock implements JsonRpcMethod { |
||||
private static final Logger LOG = LogManager.getLogger(); |
||||
|
||||
private static final String METHOD_NAME = "test_importRawBlock"; |
||||
|
||||
private final RetestethContext context; |
||||
private final JsonRpcParameter parameters; |
||||
|
||||
public TestImportRawBlock(final RetestethContext context, final JsonRpcParameter parameters) { |
||||
this.context = context; |
||||
this.parameters = parameters; |
||||
} |
||||
|
||||
@Override |
||||
public String getName() { |
||||
return METHOD_NAME; |
||||
} |
||||
|
||||
@Override |
||||
public JsonRpcResponse response(final JsonRpcRequest request) { |
||||
final String input = parameters.required(request.getParams(), 0, String.class); |
||||
final ProtocolSpec<Void> protocolSpec = context.getProtocolSpec(context.getBlockHeight()); |
||||
final ProtocolContext<Void> protocolContext = this.context.getProtocolContext(); |
||||
|
||||
final Block block; |
||||
try { |
||||
block = |
||||
Block.readFrom( |
||||
RLP.input(BytesValue.fromHexString(input)), protocolSpec.getBlockHeaderFunctions()); |
||||
} catch (final RLPException | IllegalArgumentException e) { |
||||
LOG.debug("Failed to parse block RLP", e); |
||||
return new JsonRpcSuccessResponse(request.getId(), "0x"); |
||||
} |
||||
|
||||
final BlockImporter<Void> blockImporter = protocolSpec.getBlockImporter(); |
||||
if (blockImporter.importBlock( |
||||
protocolContext, |
||||
block, |
||||
context.getHeaderValidationMode(), |
||||
context.getHeaderValidationMode())) { |
||||
return new JsonRpcSuccessResponse(request.getId(), block.getHash().toString()); |
||||
} else { |
||||
LOG.debug("Failed to import block."); |
||||
return new JsonRpcSuccessResponse(request.getId(), "0x"); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,87 @@ |
||||
/* |
||||
* Copyright 2019 ConsenSys AG. |
||||
* |
||||
* 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. |
||||
*/ |
||||
package tech.pegasys.pantheon.ethereum.retesteth.methods; |
||||
|
||||
import tech.pegasys.pantheon.ethereum.ProtocolContext; |
||||
import tech.pegasys.pantheon.ethereum.blockcreation.EthHashBlockCreator; |
||||
import tech.pegasys.pantheon.ethereum.chain.DefaultMutableBlockchain; |
||||
import tech.pegasys.pantheon.ethereum.core.Block; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockImporter; |
||||
import tech.pegasys.pantheon.ethereum.core.Wei; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.JsonRpcMethod; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; |
||||
import tech.pegasys.pantheon.ethereum.mainnet.HeaderValidationMode; |
||||
import tech.pegasys.pantheon.ethereum.mainnet.ProtocolSchedule; |
||||
import tech.pegasys.pantheon.ethereum.retesteth.RetestethClock; |
||||
import tech.pegasys.pantheon.ethereum.retesteth.RetestethContext; |
||||
import tech.pegasys.pantheon.util.bytes.BytesValue; |
||||
|
||||
import com.google.common.base.Functions; |
||||
|
||||
public class TestMineBlocks implements JsonRpcMethod { |
||||
private final RetestethContext context; |
||||
private final JsonRpcParameter parameters; |
||||
|
||||
public TestMineBlocks(final RetestethContext context, final JsonRpcParameter parameters) { |
||||
this.context = context; |
||||
this.parameters = parameters; |
||||
} |
||||
|
||||
@Override |
||||
public String getName() { |
||||
return "test_mineBlocks"; |
||||
} |
||||
|
||||
@Override |
||||
public JsonRpcResponse response(final JsonRpcRequest request) { |
||||
long blocksToMine = parameters.required(request.getParams(), 0, Long.class); |
||||
while (blocksToMine-- > 0) { |
||||
if (!mineNewBlock()) { |
||||
return new JsonRpcSuccessResponse(request.getId(), false); |
||||
} |
||||
} |
||||
|
||||
return new JsonRpcSuccessResponse(request.getId(), true); |
||||
} |
||||
|
||||
private boolean mineNewBlock() { |
||||
final RetestethClock retesethClock = context.getRetestethClock(); |
||||
final ProtocolSchedule<Void> protocolSchedule = context.getProtocolSchedule(); |
||||
final ProtocolContext<Void> protocolContext = context.getProtocolContext(); |
||||
final DefaultMutableBlockchain blockchain = context.getBlockchain(); |
||||
final HeaderValidationMode headerValidationMode = context.getHeaderValidationMode(); |
||||
final EthHashBlockCreator blockCreator = |
||||
new EthHashBlockCreator( |
||||
context.getCoinbase(), |
||||
header -> BytesValue.of(), |
||||
context.getTransactionPool().getPendingTransactions(), |
||||
protocolContext, |
||||
protocolSchedule, |
||||
Functions.identity(), |
||||
context.getEthHashSolver(), |
||||
Wei.ZERO, |
||||
blockchain.getChainHeadHeader()); |
||||
final Block block = blockCreator.createBlock(retesethClock.instant().getEpochSecond()); |
||||
|
||||
// advance clock so next mine won't hit the same timestamp
|
||||
retesethClock.advanceSeconds(1); |
||||
|
||||
final BlockImporter<Void> blockImporter = |
||||
protocolSchedule.getByBlockNumber(blockchain.getChainHeadBlockNumber()).getBlockImporter(); |
||||
return blockImporter.importBlock( |
||||
protocolContext, block, headerValidationMode, headerValidationMode); |
||||
} |
||||
} |
@ -0,0 +1,43 @@ |
||||
/* |
||||
* Copyright 2019 ConsenSys AG. |
||||
* |
||||
* 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. |
||||
*/ |
||||
package tech.pegasys.pantheon.ethereum.retesteth.methods; |
||||
|
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.JsonRpcMethod; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; |
||||
import tech.pegasys.pantheon.ethereum.retesteth.RetestethContext; |
||||
|
||||
public class TestModifyTimestamp implements JsonRpcMethod { |
||||
|
||||
private final RetestethContext context; |
||||
private final JsonRpcParameter parameters; |
||||
|
||||
public TestModifyTimestamp(final RetestethContext context, final JsonRpcParameter parameters) { |
||||
this.context = context; |
||||
this.parameters = parameters; |
||||
} |
||||
|
||||
@Override |
||||
public String getName() { |
||||
return "test_modifyTimestamp"; |
||||
} |
||||
|
||||
@Override |
||||
public JsonRpcResponse response(final JsonRpcRequest request) { |
||||
final long epochSeconds = parameters.required(request.getParams(), 0, Long.class); |
||||
context.getRetestethClock().resetTime(epochSeconds); |
||||
return new JsonRpcSuccessResponse(request.getId(), true); |
||||
} |
||||
} |
@ -0,0 +1,43 @@ |
||||
/* |
||||
* Copyright 2019 ConsenSys AG. |
||||
* |
||||
* 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. |
||||
*/ |
||||
package tech.pegasys.pantheon.ethereum.retesteth.methods; |
||||
|
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.JsonRpcMethod; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; |
||||
import tech.pegasys.pantheon.ethereum.retesteth.RetestethContext; |
||||
|
||||
public class TestRewindToBlock implements JsonRpcMethod { |
||||
private final RetestethContext context; |
||||
private final JsonRpcParameter parameters; |
||||
|
||||
public TestRewindToBlock(final RetestethContext context, final JsonRpcParameter parameters) { |
||||
this.context = context; |
||||
this.parameters = parameters; |
||||
} |
||||
|
||||
@Override |
||||
public String getName() { |
||||
return "test_rewindToBlock"; |
||||
} |
||||
|
||||
@Override |
||||
public JsonRpcResponse response(final JsonRpcRequest request) { |
||||
final long blockNumber = parameters.required(request.getParams(), 0, Long.TYPE); |
||||
|
||||
return new JsonRpcSuccessResponse( |
||||
request.getId(), context.getBlockchain().rewindToBlock(blockNumber)); |
||||
} |
||||
} |
@ -0,0 +1,142 @@ |
||||
/* |
||||
* Copyright 2018 ConsenSys AG. |
||||
* |
||||
* 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. |
||||
*/ |
||||
package tech.pegasys.pantheon.ethereum.retesteth.methods; |
||||
|
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.JsonRpcMethod; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcError; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcErrorResponse; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; |
||||
import tech.pegasys.pantheon.ethereum.retesteth.RetestethContext; |
||||
|
||||
import java.util.Iterator; |
||||
import java.util.Map; |
||||
import java.util.Optional; |
||||
|
||||
import io.vertx.core.json.JsonObject; |
||||
import org.apache.logging.log4j.LogManager; |
||||
import org.apache.logging.log4j.Logger; |
||||
|
||||
public class TestSetChainParams implements JsonRpcMethod { |
||||
|
||||
private static final Logger LOG = LogManager.getLogger(); |
||||
|
||||
public static final String METHOD_NAME = "test_setChainParams"; |
||||
private final RetestethContext context; |
||||
|
||||
public TestSetChainParams(final RetestethContext context) { |
||||
this.context = context; |
||||
} |
||||
|
||||
@Override |
||||
public String getName() { |
||||
return METHOD_NAME; |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
@Override |
||||
public JsonRpcResponse response(final JsonRpcRequest request) { |
||||
|
||||
try { |
||||
final JsonObject chainParamsAsJson = |
||||
new JsonObject((Map<String, Object>) request.getParams()[0]); |
||||
final String chainParamsAsString = chainParamsAsJson.encodePrettily(); |
||||
LOG.trace("ChainParams {}", chainParamsAsString); |
||||
final String genesisFileAsString = modifyGenesisFile(chainParamsAsString); |
||||
LOG.trace("Genesis {}", genesisFileAsString); |
||||
final boolean result = |
||||
context.resetContext( |
||||
genesisFileAsString, |
||||
chainParamsAsJson.getString("sealEngine", "NoProof"), |
||||
Optional.empty()); |
||||
|
||||
return new JsonRpcSuccessResponse(request.getId(), result); |
||||
} catch (final Exception e) { |
||||
LOG.error("Unhandled error", e); |
||||
return new JsonRpcErrorResponse(request.getId(), JsonRpcError.INVALID_PARAMS); |
||||
} |
||||
} |
||||
|
||||
private static void maybeMove( |
||||
final JsonObject src, final String srcName, final JsonObject dest, final String destName) { |
||||
if (src.containsKey(srcName)) { |
||||
dest.put(destName, src.getValue(srcName)); |
||||
src.remove(srcName); |
||||
} |
||||
} |
||||
|
||||
private static void maybeMoveToNumber( |
||||
final JsonObject src, final String srcName, final JsonObject dest, final String destName) { |
||||
if (src.containsKey(srcName)) { |
||||
dest.put(destName, Long.decode(src.getString(srcName))); |
||||
src.remove(srcName); |
||||
} |
||||
} |
||||
|
||||
private static void maybeMoveToNumber( |
||||
final JsonObject src, |
||||
final String srcName, |
||||
final JsonObject dest, |
||||
final String destName, |
||||
final long defaultValue) { |
||||
if (src.containsKey(srcName)) { |
||||
dest.put(destName, Long.decode(src.getString(srcName))); |
||||
src.remove(srcName); |
||||
} else { |
||||
dest.put(destName, defaultValue); |
||||
} |
||||
} |
||||
|
||||
private static String modifyGenesisFile(final String initialGenesis) { |
||||
final JsonObject chainParamsJson = new JsonObject(initialGenesis); |
||||
final JsonObject config = new JsonObject(); |
||||
chainParamsJson.put("config", config); |
||||
final JsonObject params = chainParamsJson.getJsonObject("params"); |
||||
final JsonObject genesis = chainParamsJson.getJsonObject("genesis"); |
||||
|
||||
// Whether sealEngine is NoProof, Ethash, or NoReward the genesis file is the same
|
||||
final JsonObject ethash = new JsonObject(); |
||||
config.put("ethash", ethash); |
||||
|
||||
maybeMoveToNumber(params, "homesteadForkBlock", config, "homesteadBlock"); |
||||
maybeMoveToNumber(params, "EIP150ForkBlock", config, "eip150Block"); |
||||
maybeMoveToNumber(params, "EIP158ForkBlock", config, "eip158Block"); |
||||
maybeMoveToNumber(params, "byzantiumForkBlock", config, "byzantiumBlock"); |
||||
maybeMoveToNumber(params, "constantinopleForkBlock", config, "constantinopleBlock"); |
||||
maybeMoveToNumber(params, "constantinopleFixForkBlock", config, "constantinopleFixBlock"); |
||||
maybeMoveToNumber(params, "istanbulForkBlock", config, "istanbulBlock"); |
||||
maybeMoveToNumber(params, "chainID", config, "chainId", 1); |
||||
maybeMove(genesis, "author", chainParamsJson, "coinbase"); |
||||
maybeMove(genesis, "difficulty", chainParamsJson, "difficulty"); |
||||
maybeMove(genesis, "extraData", chainParamsJson, "extraData"); |
||||
maybeMove(genesis, "gasLimit", chainParamsJson, "gasLimit"); |
||||
maybeMove(genesis, "mixHash", chainParamsJson, "mixHash"); |
||||
maybeMove(genesis, "nonce", chainParamsJson, "nonce"); |
||||
maybeMove(genesis, "timestamp", chainParamsJson, "timestamp"); |
||||
maybeMove(chainParamsJson, "accounts", chainParamsJson, "alloc"); |
||||
|
||||
// strip out precompiles with zero balance
|
||||
final JsonObject alloc = chainParamsJson.getJsonObject("alloc"); |
||||
final Iterator<String> fieldNamesIter = alloc.fieldNames().iterator(); |
||||
while (fieldNamesIter.hasNext()) { |
||||
final String address = fieldNamesIter.next(); |
||||
final JsonObject account = alloc.getJsonObject(address); |
||||
if (account.containsKey("precompiled") && !account.containsKey("balance")) { |
||||
fieldNamesIter.remove(); |
||||
} |
||||
} |
||||
|
||||
return chainParamsJson.encodePrettily(); |
||||
} |
||||
} |
@ -0,0 +1,83 @@ |
||||
/* |
||||
* Copyright 2019 ConsenSys AG. |
||||
* |
||||
* 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. |
||||
*/ |
||||
package tech.pegasys.pantheon.ethereum.retesteth.methods; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
import tech.pegasys.pantheon.ethereum.core.BlockHeader; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; |
||||
import tech.pegasys.pantheon.ethereum.retesteth.RetestethContext; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
import com.google.common.base.Charsets; |
||||
import com.google.common.io.Resources; |
||||
import io.vertx.core.json.JsonObject; |
||||
import org.junit.BeforeClass; |
||||
import org.junit.Test; |
||||
|
||||
public class TestSetChainParamsTest { |
||||
|
||||
private static RetestethContext context; |
||||
private static TestSetChainParams test_setChainParams; |
||||
|
||||
@BeforeClass |
||||
public static void setupClass() { |
||||
context = new RetestethContext(); |
||||
test_setChainParams = new TestSetChainParams(context); |
||||
} |
||||
|
||||
@Test |
||||
public void testValidateGenesisImport() throws IOException { |
||||
final String chainParamsJsonString = |
||||
Resources.toString( |
||||
TestSetChainParamsTest.class.getResource("multimpleBalanceInstructionChainParams.json"), |
||||
Charsets.UTF_8); |
||||
final JsonObject chainParamsJson = new JsonObject(chainParamsJsonString); |
||||
|
||||
final JsonRpcRequest request = |
||||
new JsonRpcRequest( |
||||
"2.0", TestSetChainParams.METHOD_NAME, new Object[] {chainParamsJson.getMap()}); |
||||
|
||||
assertThat(test_setChainParams.response(request)) |
||||
.isEqualTo(new JsonRpcSuccessResponse(null, true)); |
||||
|
||||
final BlockHeader blockHeader = context.getBlockHeader(0); |
||||
|
||||
assertThat(blockHeader.getLogsBloom().toString()) |
||||
.isEqualTo( |
||||
"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); |
||||
assertThat(blockHeader.getCoinbase().toString()) |
||||
.isEqualTo("0x8888f1f195afa192cfee860698584c030f4c9db1"); |
||||
assertThat(blockHeader.getDifficulty().toShortHexString()).isEqualTo("0x20000"); |
||||
assertThat(blockHeader.getExtraData().toString()).isEqualTo("0x42"); |
||||
assertThat(blockHeader.getGasLimit()).isEqualTo(3141592); |
||||
assertThat(blockHeader.getGasUsed()).isEqualTo(0); |
||||
assertThat(blockHeader.getMixHash().toString()) |
||||
.isEqualTo("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"); |
||||
assertThat(blockHeader.getNonce()).isEqualTo(0x0102030405060708L); |
||||
assertThat(blockHeader.getNumber()).isEqualTo(0); |
||||
assertThat(blockHeader.getParentHash().toString()) |
||||
.isEqualTo("0x0000000000000000000000000000000000000000000000000000000000000000"); |
||||
assertThat(blockHeader.getReceiptsRoot().toString()) |
||||
.isEqualTo("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"); |
||||
assertThat(blockHeader.getStateRoot().toString()) |
||||
.isEqualTo("0xf403922bfd555a9223f68fc755564004e20d78bb42aae647e867e3b23c48beba"); |
||||
assertThat(blockHeader.getTimestamp()).isEqualTo(0x54c98c81); |
||||
assertThat(blockHeader.getTransactionsRoot().toString()) |
||||
.isEqualTo("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"); |
||||
assertThat(blockHeader.getOmmersHash().toString()) |
||||
.isEqualTo("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"); |
||||
} |
||||
} |
@ -0,0 +1,102 @@ |
||||
{ |
||||
"accounts": { |
||||
"0x0000000000000000000000000000000000000001": { |
||||
"precompiled": { |
||||
"linear": { |
||||
"base": 3000, |
||||
"word": 0 |
||||
}, |
||||
"name": "ecrecover" |
||||
} |
||||
}, |
||||
"0x0000000000000000000000000000000000000002": { |
||||
"precompiled": { |
||||
"linear": { |
||||
"base": 60, |
||||
"word": 12 |
||||
}, |
||||
"name": "sha256" |
||||
} |
||||
}, |
||||
"0x0000000000000000000000000000000000000003": { |
||||
"precompiled": { |
||||
"linear": { |
||||
"base": 600, |
||||
"word": 120 |
||||
}, |
||||
"name": "sha256" |
||||
} |
||||
}, |
||||
"0x0000000000000000000000000000000000000004": { |
||||
"precompiled": { |
||||
"linear": { |
||||
"base": 15, |
||||
"word": 3 |
||||
}, |
||||
"name": "identity" |
||||
} |
||||
}, |
||||
"0x0000000000000000000000000000000000000005": { |
||||
"precompiled": { |
||||
"name": "modexp" |
||||
} |
||||
}, |
||||
"0x0000000000000000000000000000000000000006": { |
||||
"precompiled": { |
||||
"linear": { |
||||
"base": 500, |
||||
"word": 0 |
||||
}, |
||||
"name": "alt_bn128_G1_add" |
||||
} |
||||
}, |
||||
"0x0000000000000000000000000000000000000007": { |
||||
"precompiled": { |
||||
"linear": { |
||||
"base": 40000, |
||||
"word": 0 |
||||
}, |
||||
"name": "alt_bn128_G1_mul" |
||||
} |
||||
}, |
||||
"0x0000000000000000000000000000000000000008": { |
||||
"precompiled": { |
||||
"name": "alt_bn128_pairing_product" |
||||
} |
||||
}, |
||||
"0x095e7baea6a6c7c4c2dfeb977efac326af552d87": { |
||||
"balance": "0x0186a0", |
||||
"code": "0x73a94f5374fce5edbc8e2a8697c15331677e6ebf0b31600055738888f1f195afa192cfee860698584c030f4c9db13160015573a94f5374fce5edbc8e2a8697c15331677e6ebf0b31600255738888f1f195afa192cfee860698584c030f4c9db13160035573095e7baea6a6c7c4c2dfeb977efac326af552d8731600555", |
||||
"nonce": "0x00", |
||||
"storage": {} |
||||
}, |
||||
"0x195e7baea6a6c7c4c2dfeb977efac326af552d87": { |
||||
"balance": "0x0186a0", |
||||
"code": "0x73a94f5374fce5edbc8e2a8697c15331677e6ebf0b31600055738888f1f195afa192cfee860698584c030f4c9db13160015573a94f5374fce5edbc8e2a8697c15331677e6ebf0b31600255738888f1f195afa192cfee860698584c030f4c9db13160035573095e7baea6a6c7c4c2dfeb977efac326af552d873160045573195e7baea6a6c7c4c2dfeb977efac326af552d8731600555", |
||||
"nonce": "0x00", |
||||
"storage": {} |
||||
}, |
||||
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { |
||||
"balance": "0x1748721582", |
||||
"code": "0x", |
||||
"nonce": "0x00", |
||||
"storage": {} |
||||
} |
||||
}, |
||||
"genesis": { |
||||
"author": "0x8888f1f195afa192cfee860698584c030f4c9db1", |
||||
"difficulty": "0x020000", |
||||
"extraData": "0x42", |
||||
"gasLimit": "0x2fefd8", |
||||
"mixHash": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", |
||||
"nonce": "0x0102030405060708", |
||||
"timestamp": "0x54c98c81" |
||||
}, |
||||
"params": { |
||||
"EIP150ForkBlock": "0x00", |
||||
"EIP158ForkBlock": "0x00", |
||||
"byzantiumForkBlock": "0x00", |
||||
"homesteadForkBlock": "0x00" |
||||
}, |
||||
"sealEngine": "NoProof" |
||||
} |
@ -0,0 +1,140 @@ |
||||
/* |
||||
* Copyright 2019 ConsenSys AG. |
||||
* |
||||
* 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. |
||||
*/ |
||||
package tech.pegasys.pantheon.cli.subcommands; |
||||
|
||||
import static tech.pegasys.pantheon.cli.DefaultCommandValues.MANDATORY_HOST_FORMAT_HELP; |
||||
import static tech.pegasys.pantheon.cli.DefaultCommandValues.MANDATORY_PATH_FORMAT_HELP; |
||||
import static tech.pegasys.pantheon.cli.DefaultCommandValues.MANDATORY_PORT_FORMAT_HELP; |
||||
import static tech.pegasys.pantheon.cli.DefaultCommandValues.getDefaultPantheonDataPath; |
||||
import static tech.pegasys.pantheon.cli.subcommands.RetestethSubCommand.COMMAND_NAME; |
||||
import static tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration.DEFAULT_JSON_RPC_PORT; |
||||
|
||||
import tech.pegasys.pantheon.PantheonInfo; |
||||
import tech.pegasys.pantheon.cli.custom.JsonRPCWhitelistHostsProperty; |
||||
import tech.pegasys.pantheon.ethereum.jsonrpc.JsonRpcConfiguration; |
||||
import tech.pegasys.pantheon.ethereum.retesteth.RetestethConfiguration; |
||||
import tech.pegasys.pantheon.ethereum.retesteth.RetestethService; |
||||
|
||||
import java.net.InetAddress; |
||||
import java.nio.file.Path; |
||||
|
||||
import org.apache.logging.log4j.Level; |
||||
import org.apache.logging.log4j.LogManager; |
||||
import org.apache.logging.log4j.Logger; |
||||
import org.apache.logging.log4j.core.config.Configurator; |
||||
import picocli.CommandLine; |
||||
import picocli.CommandLine.Command; |
||||
import picocli.CommandLine.Option; |
||||
|
||||
@Command( |
||||
name = COMMAND_NAME, |
||||
description = "Run a Retesteth compatible server for reference tests.", |
||||
mixinStandardHelpOptions = true) |
||||
public class RetestethSubCommand implements Runnable { |
||||
|
||||
private static final Logger LOG = LogManager.getLogger(); |
||||
|
||||
public static final String COMMAND_NAME = "retesteth"; |
||||
|
||||
@CommandLine.Option( |
||||
names = {"--data-path"}, |
||||
paramLabel = MANDATORY_PATH_FORMAT_HELP, |
||||
description = "The path to Pantheon data directory (default: ${DEFAULT-VALUE})") |
||||
final Path dataPath = getDefaultPantheonDataPath(this); |
||||
|
||||
@Option( |
||||
names = {"--logging", "-l"}, |
||||
paramLabel = "<LOG VERBOSITY LEVEL>", |
||||
description = |
||||
"Logging verbosity levels: OFF, FATAL, WARN, INFO, DEBUG, TRACE, ALL (default: INFO)") |
||||
private final Level logLevel = Level.INFO; |
||||
|
||||
@SuppressWarnings("FieldMayBeFinal") // Because PicoCLI requires Strings to not be final.
|
||||
@Option( |
||||
names = {"--rpc-http-host"}, |
||||
paramLabel = MANDATORY_HOST_FORMAT_HELP, |
||||
description = "Host for JSON-RPC HTTP to listen on (default: ${DEFAULT-VALUE})", |
||||
arity = "1") |
||||
private String rpcHttpHost = autoDiscoverDefaultIP().getHostAddress(); |
||||
|
||||
@Option( |
||||
names = {"--rpc-http-port"}, |
||||
paramLabel = MANDATORY_PORT_FORMAT_HELP, |
||||
description = "Port for JSON-RPC HTTP to listen on (default: ${DEFAULT-VALUE})", |
||||
arity = "1") |
||||
private final Integer rpcHttpPort = DEFAULT_JSON_RPC_PORT; |
||||
|
||||
@Option( |
||||
names = {"--host-whitelist"}, |
||||
paramLabel = "<hostname>[,<hostname>...]... or * or all", |
||||
description = |
||||
"Comma separated list of hostnames to whitelist for RPC access, or * to accept any host (default: ${DEFAULT-VALUE})", |
||||
defaultValue = "localhost,127.0.0.1") |
||||
private final JsonRPCWhitelistHostsProperty hostsWhitelist = new JsonRPCWhitelistHostsProperty(); |
||||
|
||||
private InetAddress autoDiscoveredDefaultIP; |
||||
|
||||
// Used to discover the default IP of the client.
|
||||
// Loopback IP is used by default as this is how smokeTests require it to be
|
||||
// and it's probably a good security behaviour to default only on the localhost.
|
||||
private InetAddress autoDiscoverDefaultIP() { |
||||
|
||||
if (autoDiscoveredDefaultIP != null) { |
||||
return autoDiscoveredDefaultIP; |
||||
} |
||||
|
||||
autoDiscoveredDefaultIP = InetAddress.getLoopbackAddress(); |
||||
|
||||
return autoDiscoveredDefaultIP; |
||||
} |
||||
|
||||
private void prepareLogging() { |
||||
// set log level per CLI flags
|
||||
if (logLevel != null) { |
||||
System.out.println("Setting logging level to " + logLevel.name()); |
||||
Configurator.setAllLevels("", logLevel); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void run() { |
||||
prepareLogging(); |
||||
|
||||
final RetestethConfiguration retestethConfiguration = new RetestethConfiguration(dataPath); |
||||
final JsonRpcConfiguration jsonRpcConfiguration = JsonRpcConfiguration.createDefault(); |
||||
jsonRpcConfiguration.setHost(rpcHttpHost); |
||||
jsonRpcConfiguration.setPort(rpcHttpPort); |
||||
jsonRpcConfiguration.setHostsWhitelist(hostsWhitelist); |
||||
|
||||
final RetestethService retestethService = |
||||
new RetestethService(PantheonInfo.version(), retestethConfiguration, jsonRpcConfiguration); |
||||
|
||||
Runtime.getRuntime() |
||||
.addShutdownHook( |
||||
new Thread( |
||||
() -> { |
||||
try { |
||||
retestethService.close(); |
||||
LogManager.shutdown(); |
||||
} catch (final Exception e) { |
||||
LOG.error("Failed to stop Pantheon Retesteth"); |
||||
} |
||||
})); |
||||
retestethService.start(); |
||||
try { |
||||
Thread.sleep(Long.MAX_VALUE); // Is there a better way?
|
||||
} catch (final InterruptedException e) { |
||||
// e.printStackTrace();
|
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue