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

Add proper gas limit check through local computation #14707

Merged
merged 4 commits into from
Dec 13, 2024

Conversation

terencechain
Copy link
Member

This PR adds gas limit check through local computation and compare expected gas limit against builder's header. The expected gas limit is the following with adjustment_factor set to 1024

	def expected_gas_limit(parent_gas_limit, target_gas_limit, adjustment_factor):
	 max_gas_limit_difference = (parent_gas_limit // adjustment_factor) - 1
	 if target_gas_limit > parent_gas_limit:
	     gas_diff = target_gas_limit - parent_gas_limit
	     return parent_gas_limit + min(gas_diff, max_gas_limit_difference)
	 else:
	     gas_diff = parent_gas_limit - target_gas_limit
	     return parent_gas_limit - min(gas_diff, max_gas_limit_difference)

@terencechain terencechain requested a review from a team as a code owner December 10, 2024 04:29
// else:
// gas_diff = parent_gas_limit - target_gas_limit
// return parent_gas_limit - min(gas_diff, max_gas_limit_difference)
func expectedGasLimit(parentGasLimit, targetGasLimit uint64) uint64 {
Copy link
Contributor

Choose a reason for hiding this comment

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

targetGasLimit may be a bit confusing given that we have "gas limit" and "gas target" already mean something.

Copy link
Member Author

Choose a reason for hiding this comment

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

Renamed it to proposerGasLimit. Same as lighthouse

// gas_diff = parent_gas_limit - target_gas_limit
// return parent_gas_limit - min(gas_diff, max_gas_limit_difference)
func expectedGasLimit(parentGasLimit, targetGasLimit uint64) uint64 {
maxGasLimitDiff := parentGasLimit/gasLimitAdjustmentFactor - 1
Copy link
Contributor

Choose a reason for hiding this comment

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

This has an underflow if parentGasLimit < 1024, can't happen in real life though

Copy link
Member Author

Choose a reason for hiding this comment

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

return parentGasLimit + maxGasLimitDiff
}
return targetGasLimit
} else {
Copy link
Contributor

Choose a reason for hiding this comment

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

No need for an else branch here.

Copy link
Member Author

Choose a reason for hiding this comment

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

@terencechain terencechain force-pushed the check-header-gas-limit branch from 7065b2d to 77abdfd Compare December 10, 2024 14:54
Comment on lines +251 to +259
reg, err := vs.BlockBuilder.RegistrationByValidatorID(ctx, idx)
if err != nil {
log.WithError(err).Warn("Proposer: failed to get registration by validator ID, could not check gas limit")
} else {
gasLimit := expectedGasLimit(parentGasLimit, reg.GasLimit)
if gasLimit != header.GasLimit() {
return nil, fmt.Errorf("incorrect header gas limit %d != %d", gasLimit, header.GasLimit())
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

You don't seem to need this else either:

Suggested change
reg, err := vs.BlockBuilder.RegistrationByValidatorID(ctx, idx)
if err != nil {
log.WithError(err).Warn("Proposer: failed to get registration by validator ID, could not check gas limit")
} else {
gasLimit := expectedGasLimit(parentGasLimit, reg.GasLimit)
if gasLimit != header.GasLimit() {
return nil, fmt.Errorf("incorrect header gas limit %d != %d", gasLimit, header.GasLimit())
}
}
reg, err := vs.BlockBuilder.RegistrationByValidatorID(ctx, idx)
if err != nil {
log.WithError(err).Warn("Proposer: failed to get registration by validator ID, could not check gas limit")
}
gasLimit := expectedGasLimit(parentGasLimit, reg.GasLimit)
if gasLimit != header.GasLimit() {
return nil, fmt.Errorf("incorrect header gas limit %d != %d", gasLimit, header.GasLimit())
}

Copy link
Member Author

Choose a reason for hiding this comment

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

I think you need the else, we dont want to call expectedGasLimit if error

Copy link
Contributor

Choose a reason for hiding this comment

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

reg is used so if there' s an error the call to expectedGasLimit can' t be carried

potuz
potuz previously approved these changes Dec 12, 2024
@@ -243,6 +248,16 @@ func (vs *Server) getPayloadHeaderFromBuilder(ctx context.Context, slot primitiv
return nil, fmt.Errorf("incorrect parent hash %#x != %#x", header.ParentHash(), h.BlockHash())
}

reg, err := vs.BlockBuilder.RegistrationByValidatorID(ctx, idx)
Copy link
Contributor

Choose a reason for hiding this comment

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

Does the registration cache handle the case where the validator does not include a value?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think so. I went through the code as my understanding: the DefaultBuilderGasLimit is initially set in config.go:
Screenshot 2024-12-12 at 10 50 14 AM

It is then defined as 30M in mainnet_config.go:
Screenshot 2024-12-12 at 10 49 55 AM

The beacon node does not provide a flag to set this gas limit, and it is not part of the consensus spec configuration. Instead, it is hard-coded in the client. The DefaultBuilderGasLimit is also defined as a default in the validator's flag.go. If the validator does not specify the --suggested-gas-limit flag, the value in mainnet_config.go overrides it:
Screenshot 2024-12-12 at 10 52 27 AM

The DefaultBuilderGasLimit is used in proposer_setting.go's getProposerSettings function. This function displays or recreates the currently applied proposer settings under the validator's custom flags:
Screenshot 2024-12-12 at 10 54 43 AM

In loader.go, the DefaultBuilderGasLimit ensures that the default gas limit applies if the --suggested-gas-limit flag is not set. It is then saved to the proposer settings at startup when registering for the validator node service:
Screenshot 2024-12-12 at 11 00 58 AM

The SaveProposerSettings function is called within the Load method of settingsLoader in loader.go. This step saves the default gas limit to the proposer settings file:
Screenshot 2024-12-12 at 11 04 13 AM
Screenshot 2024-12-12 at 11 04 41 AM

When registering the validator client, the system ensures that the proposer settings with the correct default gas limit are stored in the database:
Screenshot 2024-12-12 at 11 05 43 AM

Choose a reason for hiding this comment

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

Not sure I get it right but it seems there is something confusing here, when using --proposer-settings-file at the validator level, if in the file if there is:

{
  "default_config": {
    "builder": {
      "enabled": false,
      "gas_limit": "42"
    },
    "fee_recipient": "XXXX"
  },
  "my_key": {
    "YYYY": {
      "builder": {
        "enabled": true
       "gas_limit": "43",
      },
      "fee_recipient": "YYYYY"
    },

When using --suggested-fee-recipient=ZZZZ, my_key will still propose on YYYY (proposer settings file has priority over flag).

However, when using --suggested-gas-limit=10, my_key will use the flag override instead of 43 (flag has priority for gas limit over proposer settings).

In processBuilderConfig, gas limit is overridden by the flag while the fee recipient is not:

Screenshot 2024-12-13 at 10 53 22

Also, if the user specifies for my_key a gas limit but no fee-recipient, it seems no fee-recipient is used? (or maybe later in the hot-path it defaults to the flag?).

It could make sense to align both behavior (to always have the file override the flags).

Copy link
Member Author

Choose a reason for hiding this comment

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

yes there's a bug there, similar bug: #14721
unrelated to this PR though so will merge this PR and fix that today

@terencechain terencechain added this pull request to the merge queue Dec 13, 2024
Merged via the queue into develop with commit fa0dc09 Dec 13, 2024
15 checks passed
@terencechain terencechain deleted the check-header-gas-limit branch December 13, 2024 16:24
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.

4 participants