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

Report L1 base fee set by L2s #111

Merged
merged 34 commits into from
Oct 2, 2023

Conversation

matYang
Copy link
Collaborator

@matYang matYang commented Sep 3, 2023

Motivation

On optimistic rollups, a major portion of tx cost is data availability fee (DA fee). DA fee = DA gas * L1 gas price.
DA gas can be calculated from message's calldata size.
L1 gas price needs to be read from the L2's L1 gas oracle.

Solution

Read L2-specific L1 gas price, encode it to PriceRegistry-compatible int format, where higher-order 112 bits are L1 gas price, and lower-order 112 bits are exec gas price.

@matYang matYang added the do not merge Do not merge at this time label Sep 3, 2023
@matYang matYang changed the title [WIP] Report L1 base fee set by L2s [WIP Sketch] Report L1 base fee set by L2s Sep 3, 2023
@matYang matYang removed the do not merge Do not merge at this time label Sep 3, 2023
@matYang matYang force-pushed the feature/offchain-report-l1-base-fee-from-l2 branch from 87eee3c to 27d328d Compare September 19, 2023 21:59

for _, obs := range observations {
if obs.SourceGasPriceUSD != nil {
if obs.SourceGasPriceUSD.notNil() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

aside: we should move this validation into getParseableObservations and we should similarly move the > r.F check into the top level report instead of spread out in 3 places. Just consider observations all or nothing

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

seems tricky, since interval | token price | gas price are validated individually, e.g. an Observation where gas price is nil can contain valid values for token price and/or interval, I think it'd still make sense to check them separately

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Communicated on Slack, decision is to reject Observations if any price field is nil, since with current impl, non-faulty nodes should not produce observations with nil fields.

NativeGasPrice: sourceNativeGasPriceUSD,
}

// If l1 oracle exists, l1 gas price is a price component of overall tx, need to fetch and encode l1 gas price.
Copy link
Collaborator

@connorwstein connorwstein Sep 21, 2023

Choose a reason for hiding this comment

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

seems very hard to follow what will happen with different versions here and the plugin doesn't need to know any of these details. I think we can abstract all of this into 2 helper components (which wrap gas.EvmGasEstimator) one for 1.0/1.1 and one for 1.2 which implement a common interface that the plugin uses. Something like:

type GasPriceEstimator interface {
	EstimateGasPrice() (GasPrice, error) // plugin calls this then EncodeGasPrice in Observe
	EncodeGasPrice(GasPrice) ([]byte, error) // encodes according to version
	DecodeGasPrice([]byte)(GasPrice, error) //  decodes according to version
}

type GasPrice interface {
	String() string // for logging
	Cmp(GasPrice) int // Used for deviation and median
}

When we instantiate the plugin:

var gasPriceEstimator GasPriceEstimator
switch commitVersion 
case 1_0, 1_1 {
    gasPriceEstimator = NewV1GasPriceEstimator() // native fees only
} case 1_2 {
    gasPriceEstimator = NewV2GasPriceEstimator() // native fees and da
} default {
	error
}
commitPlugin{gasPriceEstimator: gasPriceEstimator...}

Has an added benefit of improving testability, smaller interface injected vs the whole gas.EvmFeeEstimator and it will help reduce duplicate code in the exec plugin I think

}

if gasPriceUpdate.value != nil {
r.lggr.Infow("Latest gas price from inflight", "gasPriceUpdateVal", gasPriceUpdate.value, "gasPriceUpdateTs", gasPriceUpdate.timestamp)
if gasUpdate.value.notNil() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think clarity would be improved if we just explicitly return a boolean indicating whether there is a prior gas price update and remove this "notNil means no update"

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Can change getLatestInflightGasPriceUpdate to return (update, bool) as opposed to *update.
Per my understanding a priori gas price update where chain selector is not 0 should never contain a gas value that's nil, was this nil check more intended as defensive code?

Copy link
Collaborator Author

@matYang matYang Sep 26, 2023

Choose a reason for hiding this comment

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

Checks removed

@github-actions
Copy link
Contributor

LCOV of commit 71992a3 during Solidity Foundry #687

Summary coverage rate:
  lines......: 98.9% (911 of 921 lines)
  functions..: 96.4% (190 of 197 functions)
  branches...: 91.1% (359 of 394 branches)

Files changed coverage rate: n/a

@@ -66,6 +67,14 @@ func NewCommitServices(lggr logger.Logger, jb job.Job, chainSet evm.LegacyChainC
if err != nil {
return nil, errors.Wrap(err, "failed getting the static config from the commitStore")
}
typeAndVersion, err := commitStore.TypeAndVersion(&bind.CallOpts{})
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe we should change the loaders to also return the version, as we already parse it in LoadCommitStore

return nil
}

type CommitOffchainConfigV1 struct {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: add a comment to indicate the version range this is valid for

@github-actions
Copy link
Contributor

LCOV of commit 9b52b04 during Solidity Foundry #706

Summary coverage rate:
  lines......: 98.9% (911 of 921 lines)
  functions..: 96.4% (190 of 197 functions)
  branches...: 91.1% (359 of 394 branches)

Files changed coverage rate: n/a

@github-actions
Copy link
Contributor

LCOV of commit 3367405 during Solidity Foundry #714

Summary coverage rate:
  lines......: 98.9% (911 of 921 lines)
  functions..: 96.4% (190 of 197 functions)
  branches...: 91.1% (359 of 394 branches)

Files changed coverage rate: n/a

@github-actions
Copy link
Contributor

LCOV of commit 337a715 during Solidity Foundry #744

Summary coverage rate:
  lines......: 98.9% (915 of 925 lines)
  functions..: 96.4% (190 of 197 functions)
  branches...: 91.2% (363 of 398 branches)

Files changed coverage rate: n/a

@github-actions
Copy link
Contributor

LCOV of commit 9717f92 during Solidity Foundry #746

Summary coverage rate:
  lines......: 98.9% (915 of 925 lines)
  functions..: 96.4% (190 of 197 functions)
  branches...: 91.2% (363 of 398 branches)

Files changed coverage rate: n/a

@matYang matYang changed the title [WIP Sketch] Report L1 base fee set by L2s Report L1 base fee set by L2s Sep 28, 2023
@matYang matYang marked this pull request as ready for review September 28, 2023 04:30
@matYang matYang requested review from a team as code owners September 28, 2023 04:30
@github-actions
Copy link
Contributor

LCOV of commit c4f9718 during Solidity Foundry #753

Summary coverage rate:
  lines......: 98.9% (915 of 925 lines)
  functions..: 96.4% (190 of 197 functions)
  branches...: 91.2% (363 of 398 branches)

Files changed coverage rate: n/a

@@ -402,19 +418,18 @@ func (r *CommitReportingPlugin) getLatestTokenPriceUpdates(ctx context.Context,
return latestUpdates, nil
}

// Gets the latest gas price updates based on logs within the heartbeat
func (r *CommitReportingPlugin) getLatestGasPriceUpdate(ctx context.Context, now time.Time, checkInflight bool) (gasPriceUpdate update, error error) {
// getLatestGasPriceUpdate returns the latest gas price updates based on logs within the heartbeat.
Copy link
Contributor

Choose a reason for hiding this comment

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

typo: gas price update based

// An observation is rejected if any of its gas price or token price is nil. With current CommitObservation implementation, prices
// are checked to ensure no nil values before adding to Observation, hence an observation that contains nil values comes from a faulty node.
func (r *CommitReportingPlugin) validateObservations(ctx context.Context, lggr logger.Logger, observations []CommitObservation) (validObs []CommitObservation, err error) {
tokenDecimals, err := r.tokenDecimalsCache.Get(ctx)
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 it's somewhat better to read from token decimals cache in the caller and pass the map to this function, that way it's easier to test and easier to re-use the map.

core/services/ocr2/plugins/ccip/commit_reporting_plugin.go Outdated Show resolved Hide resolved
for token, price := range obs.TokenPricesUSD {
if price == nil {
lggr.Warnw("Nil value in observed TokenPricesUSD", "token", token.Hex())
skipObservation = true
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe it's safe to stop on the first skipped observation?

i.e. with something like this

func skipObservation() bool {
   for token, price := range obs.TokenPricesUSD {
      if price==nil {
           return false
      }

      if _, exists := tokenDecimals[token]; !exists {
          return false
      }
   }
   return true
}

Copy link
Collaborator

@connorwstein connorwstein Sep 29, 2023

Choose a reason for hiding this comment

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

yeah agreed I think we can just continue on first invalidation

Copy link
Collaborator Author

@matYang matYang Sep 29, 2023

Choose a reason for hiding this comment

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

This was on purpose, as nil prices should not happen - it indicates a bug or a faulty node, normally this should never trigger, when it does, printing all nil values will help with triaging.

func (g ExecGasPriceEstimator) Median(gasPrices []GasPrice) (GasPrice, error) {
var prices []*big.Int
for _, p := range gasPrices {
if p != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

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

the rest of the implementations assume non-nil input. I think we either make the input trusted to be not-nil (i.e. document that in the interface) and don't check it or we check it in all the impls and return an error. Probably the former since we validate input in the plugin

for token, price := range obs.TokenPricesUSD {
if price == nil {
lggr.Warnw("Nil value in observed TokenPricesUSD", "token", token.Hex())
skipObservation = true
Copy link
Collaborator

@connorwstein connorwstein Sep 29, 2023

Choose a reason for hiding this comment

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

yeah agreed I think we can just continue on first invalidation

// Updating EVM2EVMMessage involves an offchain upgrade, safe to keep this as constant in code.
EvmMessageFixedBytes = 448
EvmMessageBytesPerToken = 128 // Byte size of each token transfer, consisting of 1 EVMTokenAmount and 1 bytes, excl length of bytes
DAMultiplierBase = int64(10000) // DA multiplier is in multiples of 0.0001, i.e. 1/DAMultiplierBase
Copy link
Collaborator

Choose a reason for hiding this comment

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

shouldn't need to expose these version specific details from the prices package?

@github-actions
Copy link
Contributor

LCOV of commit f619ede during Solidity Foundry #767

Summary coverage rate:
  lines......: 98.9% (915 of 925 lines)
  functions..: 96.4% (190 of 197 functions)
  branches...: 91.2% (363 of 398 branches)

Files changed coverage rate: n/a

@github-actions
Copy link
Contributor

LCOV of commit 42fa3bd during Solidity Foundry #771

Summary coverage rate:
  lines......: 98.9% (915 of 925 lines)
  functions..: 96.4% (190 of 197 functions)
  branches...: 91.2% (363 of 398 branches)

Files changed coverage rate: n/a

@github-actions
Copy link
Contributor

github-actions bot commented Oct 2, 2023

LCOV of commit 9d7673f during Solidity Foundry #773

Summary coverage rate:
  lines......: 98.9% (915 of 925 lines)
  functions..: 96.4% (190 of 197 functions)
  branches...: 91.2% (363 of 398 branches)

Files changed coverage rate: n/a

@github-actions
Copy link
Contributor

github-actions bot commented Oct 2, 2023

LCOV of commit db7be89 during Solidity Foundry #775

Summary coverage rate:
  lines......: 98.9% (915 of 925 lines)
  functions..: 96.4% (190 of 197 functions)
  branches...: 91.2% (363 of 398 branches)

Files changed coverage rate: n/a

- the new `sourceTokenData` field is added to the hash.
- fixed-size fields are hashed in nested hash function.
- CommitStore OffchainConfig fields updated.
- New fields `GasPriceHeartBeat`, `DAGasPriceDeviationPPB`, `ExecGasPriceDeviationPPB`, `TokenPriceHeartBeat`, `TokenPriceDeviationPPB` added
Copy link
Collaborator

Choose a reason for hiding this comment

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

could use some more detail on the usage of these new params, can be follow up to unblock

@matYang matYang merged commit 37b87f0 into ccip-develop Oct 2, 2023
49 checks passed
@matYang matYang deleted the feature/offchain-report-l1-base-fee-from-l2 branch October 2, 2023 15:11
asoliman92 pushed a commit that referenced this pull request Jul 31, 2024
* CCIP plugin code sketch

* revert models changes

* update gas price encoding and deviation

* udpate exec cost estimation

* Update exec cost estimation

* update offchain config

* use gas price struct instead of encoding where applicable

* do not use v2 suffix for latest version of offchain config

* add nil check for gas update

* Adding price estimator interfaces

* update commit plugin

* update name to prices

* update exec plugin

* comments and nits

* change the loaders to also return version

* address comments

* making tests build

* validate observed token prices match token with decimals

* fixed commit reporting tests

* add test for validateObservations

* pass tests

* add test for exec getPrice

* couple more tests in exec price estimator

* complete exec price estimator test

* add da price estimator test

* resolve exec logpoller fiter test

* address comments

* remove price estimator deviation and computeCost opts

* fix 1.21 import
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