Skip to content
This repository has been archived by the owner on Sep 26, 2019. It is now read-only.

[PAN-2946] Retesteth support #1818

Merged
merged 61 commits into from
Aug 7, 2019
Merged

[PAN-2946] Retesteth support #1818

merged 61 commits into from
Aug 7, 2019

Conversation

shemnon
Copy link
Contributor

@shemnon shemnon commented Aug 3, 2019

PR description

Add support for retesteth, a JSON-RPC based reference testing API.

Fixed Issue(s)

PAN-2949 PAN-2950 PAN-2951 PAN-2935 PAN-2954 PAN-2955

shemnon added 30 commits July 12, 2019 20:59
`./retesteth/retesteth -t BlockchainTests/bcStateTests -- --testpath ../../tests/ --clients pantheon`

I suspect DebugStorageRangeAt is most of the problem.
Use GenesisState to do a lot of what was done by hand.
* Add header validation mode based on seal engine
* rework account storage range, input is in decimal, so use existing super class
* catch another invalid RLP
* set the timestamp of the system (requires a genesis rebuild)
* add a raw transaction
* then mine the blocks.
# Conflicts:
#	ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/ProtocolScheduleBuilder.java
All tests run.  Not all tests pass.  There's some stuff relating to mining
and protocol schedules I need to fix.
I nede to intercept several classes to get the block reward change everywhere
it needs to be.
# Conflicts:
#	ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/worldstate/DebuggableMutableWorldState.java
#	ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/worldstate/WorldStateArchive.java
#	ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/results/DebugStorageRangeAtResult.java
#	ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugStorageRangeAtTest.java
# Conflicts:
#	ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/worldstate/DefaultMutableWorldState.java
# Conflicts:
#	ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockCreator.java
#	ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/headervalidationrules/ProofOfWorkValidationRule.java
shemnon added 11 commits August 1, 2019 20:09
# Conflicts:
#	util/src/main/java/tech/pegasys/pantheon/util/uint/UInt256Value.java
# Conflicts:
#	ethereum/blockcreation/src/main/java/tech/pegasys/pantheon/ethereum/blockcreation/AbstractBlockCreator.java
#	ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetBlockProcessor.java
Some of the methods need to have a changeable reference to stuff like
blockchainqueries.  For the impacted methods the solution is to wrap them
in a Supplier<> interface.  This includes new constructors for re-used methods.

Also include the debug_accountRangeAt method as it's namespaced as debug.

Also, rename the getter methods to align with standards.
# Conflicts:
#	ethereum/blockcreation/src/test/java/tech/pegasys/pantheon/ethereum/blockcreation/EthHashBlockCreatorTest.java
* restore missing spec tests
* rename to debig_accountRange
* make sure stack trace come with invalidjsonrpcparams catch
# Conflicts:
#	ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/chain/DefaultMutableBlockchain.java
#	ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugAccountRange.java
#	ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugStorageRangeAt.java
#	ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/results/BlockResult.java
#	ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/results/BlockResultFactory.java
#	ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/results/DebugStorageRangeAtResult.java
#	ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/EthJsonRpcHttpBySpecTest.java
@shemnon
Copy link
Contributor Author

shemnon commented Aug 3, 2019

Currently works with retesteth at branch 9a4609a5d7d84b61734c2dbb5d2d8cf4981b33e0

Copy link
Contributor

@mbaxter mbaxter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't finished looking over everything - just dropping in a few comments for now 😄

public class NoProofSolver extends EthHashSolver {

NoProofSolver(final Iterable<Long> nonceGenerator) {
super(nonceGenerator, null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about creating an EthHashSolver interface, so we don't have to set null values in the superclass? Doesn't look like you're using anything significant from the superclass? Then the implementation EthHashSolver becomes DefaultEthHashSolver.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An interface already exists, and it's a functional interface. Reduced it to a lambda.

final ProtocolSpec<C> original = delegate.getByBlockNumber(number);
// Pre Spurious Dragon we need to "touch" the accounts to create a zero-balance version. We use
// the maximum possible reward as a sentinel value.
final boolean skipZeroBlockRewards = !TOUCH_ACCOUNT_SCHEDULES.contains(original.getName());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you just use:

Suggested change
final boolean skipZeroBlockRewards = !TOUCH_ACCOUNT_SCHEDULES.contains(original.getName());
final boolean skipZeroBlockRewards = original.isSkipZeroBlockRewards();

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. Style tweak to eliminate the variable since it's not calculated.

public ProtocolSpec<C> getByBlockNumber(final long number) {
final ProtocolSpec<C> original = delegate.getByBlockNumber(number);
// Pre Spurious Dragon we need to "touch" the accounts to create a zero-balance version. We use
// the maximum possible reward as a sentinel value.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like an outdated comment here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

original.getTransactionReceiptFactory(),
original.getDifficultyCalculator(),
Wei.ZERO, // block reward
null, // transaction receipt type; unused
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should remove this param or set something here - thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a reasonable activity, but that kind of refactor should stand on its own PR IMHO.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turns out this is to support EIP-658, so no it cannot be removed. Perhaps a different comment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you look at the ProtocolSpec constructor, this field is never actually used so looks like it can be removed safely. Alternatively a less invasive change might be to add a getter for the field and set the value here to origin.getReceiptType() (plus log a ticket to remove later).

As a general principle, I want to avoid using nulls even if it makes sense right now because over time it will make the code more brittle and we've done a pretty good job of avoiding nulls up to this point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in #1825

private EthHashSolver ethHashSolver;

public boolean resetContext(
final String genesisConfigString, final String sealEngine, final Optional<Long> clockTime) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about creating an enum for sealEngine rather than just letting it be any string?

Copy link
Contributor Author

@shemnon shemnon Aug 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concern is how do we deal with new random values? The testing team has already shown that they will introduce random undocumented values ("NoReward") and I expect at least one other arbitrary add ("ProgPow" or some variant of the name). If we make an enum then unexpected test evolution becomes a parsing error and we fail with an RPC exception instead of bad data. Retesteh is already bad when it comes to reporting back data on the RPC failures (i.e. which test tripped it) but is much better about state failures.

So if PegaSys was controlling the code I'd say yes, but I don't feel we can trust the community code will behave well.

Copy link
Contributor

@mbaxter mbaxter Aug 6, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍Makes sense to me as-is then


protocolSchedule =
MainnetProtocolSchedule.fromConfig(
JsonGenesisConfigOptions.fromJsonObject((ObjectNode) genesisConfig.get("config")));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(optional) There's also JsonUtil.getObjectNode which avoids the casting :D

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

original.getTransactionReceiptFactory(),
original.getDifficultyCalculator(),
Wei.ZERO, // block reward
null, // transaction receipt type; unused
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you look at the ProtocolSpec constructor, this field is never actually used so looks like it can be removed safely. Alternatively a less invasive change might be to add a getter for the field and set the value here to origin.getReceiptType() (plus log a ticket to remove later).

As a general principle, I want to avoid using nulls even if it makes sense right now because over time it will make the code more brittle and we've done a pretty good job of avoiding nulls up to this point.


blockchainQueries = new BlockchainQueries(blockchain, worldStateArchive);

final String sealengine = JsonUtil.getString(genesisConfig, "sealengine", "");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth normalizing the sealengine value with toLowerCase().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sealEngine values are canonically mixed case, per the docs that do cover it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just makes everything more stable if casing inconsistencies crop up even if there's a supposed canonical form for each value.

/* Converts all to lowercase for easier lookup since the keys in a 'genesis.json' file are assumed
* case insensitive.
*/
private static ObjectNode normalizeKeys(final ObjectNode genesis) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth deduping this method by moving it to JsonUtil.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll do this and the receiptType in another PR by the end of the day and then merge them in.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in #1826

final JsonRpcParameter parameters = new JsonRpcParameter();
final BlockResultFactory blockResult = new BlockResultFactory();
final Map<String, JsonRpcMethod> jsonRpcMethods = new HashMap<>();
JsonRpcMethodsFactory.addMethods(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(nit) In retrospect seems unnecessary to publicly expose this method, when it's just looping over some methods and adding them to a hashmap.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's either expose it publicly or duplicate it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I'd duplicate - its not a lot of code 🤷‍♀

context.getBlockchainQueries().transactionReceiptByTransactionHash(txHash);
return new JsonRpcSuccessResponse(
request.getId(),
receipt.map(this::calculateLogHash).orElse(Hash.EMPTY_LIST_HASH).toString());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is Hash.EMPTY_LIST_HASH the right default? The general json-rpc pattern has been to return null if a value isn't found. The hash of empty list implies that the receipt was found and has no logs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what retesteth specified when they defined the method. It does feel like an implementation detail leaking through but returning null breaks the RPC tests.

public JsonRpcResponse response(final JsonRpcRequest request) {
long blocksToMine = parameters.required(request.getParams(), 0, Long.class);
while (blocksToMine-- > 0) {
if (!context.mineNewBlock()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels like this mineNewBlock method should be a method in this class, rather than sitting in context. When I see a "context" class I don't usually expect it to be doing real work, but just providing access to dependencies.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@Override
public JsonRpcResponse response(final JsonRpcRequest request) {
final long epochSeconds = parameters.required(request.getParams(), 0, Long.class);
context.setTimestamp(epochSeconds);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here - seems like you should be getting the clock from context, and resetting the value in this method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

public JsonRpcResponse response(final JsonRpcRequest request) {
final long blockNumber = parameters.required(request.getParams(), 0, Long.TYPE);

return new JsonRpcSuccessResponse(request.getId(), context.rewindToBlock(blockNumber));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above - seems like you should be getting the blockchain from context and then rewinding here. Or alternatively, maybe context has an "actions" object that holds various retesteth actions? Seems more cohesive to just house the logic here though unless there's a lot of reuse.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

import com.google.common.base.Charsets;
import com.google.common.io.Resources;

class RetestethFixtures {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this class is unused.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, when I duplicated the RPC methods I also duplicated the test classes and testing support. Looks like this is indeed dead and should be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Contributor

@mbaxter mbaxter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me!


blockchainQueries = new BlockchainQueries(blockchain, worldStateArchive);

final String sealengine = JsonUtil.getString(genesisConfig, "sealengine", "");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just makes everything more stable if casing inconsistencies crop up even if there's a supposed canonical form for each value.

final JsonRpcParameter parameters = new JsonRpcParameter();
final BlockResultFactory blockResult = new BlockResultFactory();
final Map<String, JsonRpcMethod> jsonRpcMethods = new HashMap<>();
JsonRpcMethodsFactory.addMethods(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I'd duplicate - its not a lot of code 🤷‍♀

@shemnon shemnon merged commit 2d478de into PegaSysEng:master Aug 7, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants