Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unified protocol schedule - combine TimestampSchedule and MutableProtocolSchedule #5310

Merged
merged 29 commits into from
Apr 20, 2023

Conversation

siladu
Copy link
Contributor

@siladu siladu commented Apr 4, 2023

This is a necessarily large PR, but it's mostly type removal/renaming/unification changes.
I have added comments to highlight the interesting parts that require closer review.

Currently, one reference test file breaking due to invalid test inputs see #5310 (comment)

PR description

  • Combine MutableProtocolSchedule and DefaultTimestampSchedule into UnifiedProtocolSchedule.
  • Implement getByBlockHeader taking into account both timestamp-based and blockNumber-based forks
  • Unstitch timestampSchedule from TransitionProtocolSchedule
  • BftProtocolSchedule extends UnifiedProtocolSchedule (instead of MutableProtocolSchedule)
  • [DONE] Optimise getByBlockHeader algorithm (once all tests are passing)

Follow up PR will cover the more minor tidy up changes left out of this PR for brevity: #5354

Should end up with this hierarchy:

                            ----> PrivacySupportingProtocolSchedule 
                           |
                           | 
                   ProtocolSchedule
                    - public getByBlockHeader
                           ^ 
                           |
         ----->    UnifiedProtocolSchedule  <------ ---------------------------------
        |           - public getByBlockHeader      |                                 |
        |           - private getByTimestamp       |                                 |
        |           - private getByBlockNumber     |                                 |
        |                                          |                                 |
        |                                          |                                 |
BftProtocolSchedule                MilestoneStreamingProtocolSchedule    TransitionProtocolSchedule
 - public getByBlockNumber          - public streamMilestones             - public getByBlockNumber

Fixed Issue(s)

#5260

Testing

Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
…erenceTests

handling TODOs/comments tidy up now before going back to fixing tests as want to put on CI

Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
@github-actions
Copy link

github-actions bot commented Apr 4, 2023

  • I thought about documentation and added the doc-change-required label to this PR if updates are required.
  • I have considered running ./gradlew acceptanceTestNonMainnet locally if my PR affects non-mainnet modules.
  • I thought about the changelog and included a changelog update if required.


import com.google.common.annotations.VisibleForTesting;

public class UnifiedProtocolSchedule implements ProtocolSchedule {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Plan to rename this DefaultProtocolSchedule but think Unified helps with clarity for the first pass review

Comment on lines 100 to -120
@Override
public ProtocolSpec getByBlockHeader(final ProcessableBlockHeader blockHeader) {
return this.timestampSchedule
.getByTimestamp(blockHeader.getTimestamp())
.orElseGet(
() ->
transitionUtils.dispatchFunctionAccordingToMergeState(
protocolSchedule -> protocolSchedule.getByBlockHeader(blockHeader)));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

getByTimestamp and getByBlockNumber are stitched together inside UnifiedProtocolSchedule now, so complexity is encapsulated in that one place.

Comment on lines -191 to -201
public ProtocolSpec getByBlockNumber(final long number) {

return Optional.ofNullable(protocolContext)
.map(ProtocolContext::getBlockchain)
.flatMap(blockchain -> blockchain.getBlockHeader(number))
.map(timestampSchedule::getByBlockHeader)
.orElseGet(
() ->
transitionUtils.dispatchFunctionAccordingToMergeState(
protocolSchedule -> protocolSchedule.getByBlockNumber(number)));
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

getByTimestamp and getByBlockNumber are stitched together inside UnifiedProtocolSchedule.getByBlockHeader now, so complexity is encapsulated in that one place.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

AbstractProtocolScheduleBuilder only has one subclass now, ProtocolScheduleBuilder but will merge them in subsequent PR to minimise this already rather large changeset :)

Comment on lines 124 to +130
validateForkOrder("GrayGlacier", config.getGrayGlacierBlockNumber(), lastForkBlock);
// Begin timestamp forks
lastForkBlock = validateForkOrder("Shanghai", config.getShanghaiTime(), lastForkBlock);
lastForkBlock = validateForkOrder("Cancun", config.getCancunTime(), lastForkBlock);
lastForkBlock = validateForkOrder("FutureEips", config.getFutureEipsTime(), lastForkBlock);
lastForkBlock =
validateForkOrder("ExperimentalEips", config.getExperimentalEipsTime(), lastForkBlock);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

append timestamp forks to end of blockNumber forks

Comment on lines 172 to 179
// Timestamp Forks
createTimestampMilestone(config.getShanghaiTime(), specFactory.shanghaiDefinition(config)),
createTimestampMilestone(config.getCancunTime(), specFactory.cancunDefinition(config)),
createTimestampMilestone(
config.getFutureEipsTime(), specFactory.futureEipsDefinition(config)),
createTimestampMilestone(
config.getExperimentalEipsTime(), specFactory.experimentalEipsDefinition(config)),

Copy link
Contributor Author

Choose a reason for hiding this comment

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

append timestamp forks to end of blockNumber forks.

Using createTimestampMilestone because the getBlockByHeader algorithm needs to know the difference between timestamp and blockNumber forks.


@Override
public ProtocolSpec getByBlockHeader(final ProcessableBlockHeader blockHeader) {
// checkArgument(number >= 0, "number must be non-negative");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Think we can safely assume that a blockHeader will never have a negative number or timestamp

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 that's a reasonable assumption and the BlockHeaderBuilder checks the number and timestamp are >= 0

Comment on lines 42 to 93
// TODO SLD - do we need this or a getByBlockHeader equivalent? Could be why referenceTests are
// failing?
// @Override
// public ProtocolSpec getByBlockNumber(final long number) {
// final ProtocolSpec original = delegate.getByBlockNumber(number);
// final BlockProcessor noRewardBlockProcessor =
// new MainnetBlockProcessor(
// original.getTransactionProcessor(),
// original.getTransactionReceiptFactory(),
// Wei.ZERO,
// original.getMiningBeneficiaryCalculator(),
// original.isSkipZeroBlockRewards(),
// Optional.empty(),
// delegate);
// final BlockValidator noRewardBlockValidator =
// new MainnetBlockValidator(
// original.getBlockHeaderValidator(),
// original.getBlockBodyValidator(),
// noRewardBlockProcessor,
// original.getBadBlocksManager());
// final BlockImporter noRewardBlockImporter = new
// MainnetBlockImporter(noRewardBlockValidator);
// return new ProtocolSpec(
// original.getName(),
// original.getEvm(),
// original.getTransactionValidator(),
// original.getTransactionProcessor(),
// original.getPrivateTransactionProcessor(),
// 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(),
// original.getGasCalculator(),
// original.getGasLimitCalculator(),
// original.getFeeMarket(),
// original.getBadBlocksManager(),
// Optional.empty(),
// original.getWithdrawalsValidator(),
// original.getWithdrawalsProcessor(),
// original.getDepositsValidator(),
// original.isPoS());

// }
Copy link
Contributor Author

@siladu siladu Apr 5, 2023

Choose a reason for hiding this comment

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

Do we need this or a getByBlockHeader equivalent? Could be why referenceTests are failing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@shemnon I've essentially migrated getBlockByNumber to getBlockByHeader.
Do you know if the logic that I've commented out in here is actually used?

It's not covered by tests AFAICT, but I think it might be used if retesteth is run with certain inputs.

Copy link
Contributor

Choose a reason for hiding this comment

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

This will break retesteth and we won't be able to fill reference tests.

retesteth needs to have some blocks evaluated "without block reward" and this is how that feature is wired in.

Copy link
Contributor Author

@siladu siladu Apr 6, 2023

Choose a reason for hiding this comment

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

I think I can just migrate this code into the getByBlockHeader method above then. I think the break has already occurred during this commit actually, since that implementation of getBlockByNumber is no longer used: 62f4a51

Copy link
Contributor Author

Choose a reason for hiding this comment

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

IIRC this was the beyoncé rule one 😉 😆

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've just migrated it here, hope this makes sense! b459bc3

Copy link
Contributor

Choose a reason for hiding this comment

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

what's the beyoncé rule??

Copy link
Contributor Author

Choose a reason for hiding this comment

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

String listMilestones();

void putMilestone(
final boolean isTimestampMilestone,
Copy link
Contributor Author

@siladu siladu Apr 5, 2023

Choose a reason for hiding this comment

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

Would rather find a more encapsulated way to deal with this, ideally private to UnifiedProtocolSchedule (only used by getByBlockHeader). It does make UnifiedProtocolSchedule.getByBlockHeader implementation quite neat though.

Copy link
Contributor

Choose a reason for hiding this comment

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

could move this into another interface like what was done with the mutable methods in the current implementation at least then it would only be exposed for the few places it's needed like the protocol schedule builder.

Signed-off-by: Simon Dudley <simon.dudley@consensys.net>

Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
…hedule

Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
@siladu
Copy link
Contributor Author

siladu commented Apr 5, 2023

@shemnon There is a single referenceTest file that is breaking:

Now that I've combined the timestamp milestones into the same collection as the blockNumber milestone, there's a difficulty mismatch because the protocolSchedule retrieves Shanghai instead of the appropriate blockNumber milestone.

This is because the test inputs have timestamps far into the future so it always matches Shanghai.

For example, the first entry:

 "DifficultyTest1" : {
		"parentTimestamp" : "0x036d2f0c29",
		"parentDifficulty" : "0x17f87b030686276f",
		"parentUncles" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
		"currentTimestamp" : "0x036d2f0c29",
		"currentBlockNumber" : "0x0186a0",
		"currentDifficulty" : "0x17fb7a1266e6f833"
	},

The timestamp 0x036d2f0c29 is 14716701737 which is Fri May 09 2436 04:42:17

Any thoughts about how we could fix this?

Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
…hedule

Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
This reverts commit 861ab1d.

Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
@siladu
Copy link
Contributor Author

siladu commented Apr 5, 2023

@shemnon I've opened an issue for now ethereum/tests#1207 but is it possible/appropriate to temporarily disable these while we wait so it doesn't block the PR?

Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
Comment on lines 56 to 74
public ProtocolSpec getByBlockHeader(final ProcessableBlockHeader blockHeader) {
// TODO SLD to remove once it's been discussed in PR review
// checkArgument(number >= 0, "number must be non-negative");
checkArgument(
!protocolSpecs.isEmpty(), "At least 1 milestone must be provided to the protocol schedule");
checkArgument(
protocolSpecs.last().milestone() == 0, "There must be a milestone starting from block 0");

// protocolSpecs is sorted in descending block order, so the first one we find that's lower than
// the requested level will be the most appropriate spec
for (final ScheduledProtocolSpec scheduledProtocolSpec : protocolSpecs) {
if (scheduledProtocolSpec.isTimestampMilestone()) {
if (blockHeader.getTimestamp() >= scheduledProtocolSpec.milestone()) {
return scheduledProtocolSpec.spec();
}
} else {
if (blockHeader.getNumber() >= scheduledProtocolSpec.milestone()) {
return scheduledProtocolSpec.spec();
}
Copy link
Contributor Author

@siladu siladu Apr 5, 2023

Choose a reason for hiding this comment

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

This is the most important change and represents a change to the algorithm for retrieving the protocol schedule: it still uses the original MutableScheduleProtocol.getByBlockNumber algorithm (running through the schedule in reverse order and returning as soon as our input milestone is beyond a spec), however it checks the timestamp if it's a timestampFork or else it checks the blockNumber.

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 the original MutableProtocolSchedule.getByBlockNumber algorithm for comparison:

  @Override
  public ProtocolSpec getByBlockNumber(final long number) {
    checkArgument(number >= 0, "number must be non-negative");
    checkArgument(
        !protocolSpecs.isEmpty(), "At least 1 milestone must be provided to the protocol schedule");
    checkArgument(
        protocolSpecs.last().milestone() == 0, "There must be a milestone starting from block 0");
    // protocolSpecs is sorted in descending block order, so the first one we find that's lower than
    // the requested level will be the most appropriate spec
    for (final ScheduledProtocolSpec s : protocolSpecs) {
      if (number >= s.milestone()) {
        return s.spec();
      }
    }
    return null;
  }

Copy link
Contributor

Choose a reason for hiding this comment

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

nit: you could make this closer to the original and remove a little bit of dupe by doing something like

    for (final ScheduledProtocolSpec scheduledProtocolSpec : protocolSpecs) {
      final long milestone =
          scheduledProtocolSpec.isTimestampMilestone()
              ? blockHeader.getTimestamp()
              : blockHeader.getNumber();
      if (milestone >= scheduledProtocolSpec.milestone()) {
        return scheduledProtocolSpec.spec();
      }
    }

@shemnon
Copy link
Contributor

shemnon commented Apr 6, 2023

@shemnon I've opened an issue for now ethereum/tests#1207 but is it possible/appropriate to temporarily disable these while we wait so it doesn't block the PR?

Looks to like the tests for difficulty are not being maintained.

In the interrum we can change the shanghai timstamp in the mainnet config

  Arguments.of(
            "/BasicTests/difficultyMainNetwork.json",
            MainnetProtocolSchedule.fromConfig(
                GenesisConfigFile.mainnet()
                    .getConfigOptions(Map.of("shanghaiTime", "999999999999")),
                EvmConfiguration.DEFAULT)),

Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
…steth NoReward

Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
@siladu
Copy link
Contributor Author

siladu commented Apr 13, 2023

This failed testing because there's a bug where MainnetEVMs.paris is applied to shanghai due to #4788
I intend to play #4788 first or as part of this.

Copy link
Contributor

@macfarla macfarla left a comment

Choose a reason for hiding this comment

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

a few comments

siladu and others added 7 commits April 14, 2023 12:31
Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
…hedule

Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
…net/UnifiedProtocolScheduleTest.java

Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>
Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
…net/UnifiedProtocolScheduleTest.java

Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>
Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
Instead of applying mergeSpecificModifications to every post-merge fork, rely on what's configured in the MainnetProtocolsSpec.
The exception being MergeBlockProcessor which still needs wiring in as modification due to circular dependency.
Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
…hedule

Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
@siladu siladu marked this pull request as ready for review April 17, 2023 03:08
Copy link
Contributor

@macfarla macfarla left a comment

Choose a reason for hiding this comment

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

LGTM


@Override
public ProtocolSpec getByBlockHeader(final ProcessableBlockHeader blockHeader) {
// checkArgument(number >= 0, "number must be non-negative");
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 that's a reasonable assumption and the BlockHeaderBuilder checks the number and timestamp are >= 0

String listMilestones();

void putMilestone(
final boolean isTimestampMilestone,
Copy link
Contributor

Choose a reason for hiding this comment

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

could move this into another interface like what was done with the mutable methods in the current implementation at least then it would only be exposed for the few places it's needed like the protocol schedule builder.

String listMilestones();

void putMilestone(
final boolean isTimestampMilestone,
Copy link
Contributor

Choose a reason for hiding this comment

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

really don't like this boolean, maybe enum would make it clearer

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Used inheritance instead as discussed 816137a

Comment on lines 56 to 74
public ProtocolSpec getByBlockHeader(final ProcessableBlockHeader blockHeader) {
// TODO SLD to remove once it's been discussed in PR review
// checkArgument(number >= 0, "number must be non-negative");
checkArgument(
!protocolSpecs.isEmpty(), "At least 1 milestone must be provided to the protocol schedule");
checkArgument(
protocolSpecs.last().milestone() == 0, "There must be a milestone starting from block 0");

// protocolSpecs is sorted in descending block order, so the first one we find that's lower than
// the requested level will be the most appropriate spec
for (final ScheduledProtocolSpec scheduledProtocolSpec : protocolSpecs) {
if (scheduledProtocolSpec.isTimestampMilestone()) {
if (blockHeader.getTimestamp() >= scheduledProtocolSpec.milestone()) {
return scheduledProtocolSpec.spec();
}
} else {
if (blockHeader.getNumber() >= scheduledProtocolSpec.milestone()) {
return scheduledProtocolSpec.spec();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: you could make this closer to the original and remove a little bit of dupe by doing something like

    for (final ScheduledProtocolSpec scheduledProtocolSpec : protocolSpecs) {
      final long milestone =
          scheduledProtocolSpec.isTimestampMilestone()
              ? blockHeader.getTimestamp()
              : blockHeader.getNumber();
      if (milestone >= scheduledProtocolSpec.milestone()) {
        return scheduledProtocolSpec.spec();
      }
    }

Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
@siladu siladu mentioned this pull request Apr 17, 2023
6 tasks
Move timestamp or blockNumber complexity into ScheduledProtocolSpec subclasses
Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
…hedule

Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
@siladu siladu requested a review from jframe April 18, 2023 22:29
Copy link
Contributor

@jframe jframe left a comment

Choose a reason for hiding this comment

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

LGTM

@@ -107,8 +107,7 @@ public Map<String, JsonRpcMethod> methods(
blockchainQueries, protocolSchedule, transactionPool, privacyParameters),
new ExecutionEngineJsonRpcMethods(
miningCoordinator,
MergeProtocolSchedule.createTimestamp(
genesisConfigOptions, privacyParameters, false),
MergeProtocolSchedule.create(genesisConfigOptions, privacyParameters, false),
Copy link
Contributor

Choose a reason for hiding this comment

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

Now that the MergeProtocolShedule already contains the timestamp forks this creation might not be needed anymore as discussed. Might be useful to try use the existing one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Created a task on the other PR #5354

@siladu siladu merged commit f8a7d87 into hyperledger:main Apr 20, 2023
@siladu siladu deleted the unified-protocol-schedule branch April 20, 2023 08:24
elenduuche pushed a commit to elenduuche/besu that referenced this pull request Aug 16, 2023
…ocolSchedule (hyperledger#5310)

- Combine MutableProtocolSchedule and DefaultTimestampSchedule into UnifiedProtocolSchedule.
- Implement getByBlockHeader taking into account both timestamp-based and blockNumber-based forks
- Unstitch timestampSchedule from TransitionProtocolSchedule
- BftProtocolSchedule extends UnifiedProtocolSchedule (instead of MutableProtocolSchedule)
- In MergeProtocolSchedule, unapply mergeSpecificModifications from Shanghai onwards
- Migrate getByBlockNumber modifications into getByBlockHeader for retesteth NoReward
- Temporarily fix reference tests by artificially increasing shanghaiTime

Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>
eum602 pushed a commit to lacchain/besu that referenced this pull request Nov 3, 2023
…ocolSchedule (hyperledger#5310)

- Combine MutableProtocolSchedule and DefaultTimestampSchedule into UnifiedProtocolSchedule.
- Implement getByBlockHeader taking into account both timestamp-based and blockNumber-based forks
- Unstitch timestampSchedule from TransitionProtocolSchedule
- BftProtocolSchedule extends UnifiedProtocolSchedule (instead of MutableProtocolSchedule)
- In MergeProtocolSchedule, unapply mergeSpecificModifications from Shanghai onwards
- Migrate getByBlockNumber modifications into getByBlockHeader for retesteth NoReward
- Temporarily fix reference tests by artificially increasing shanghaiTime

Signed-off-by: Simon Dudley <simon.dudley@consensys.net>
Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants