-
Notifications
You must be signed in to change notification settings - Fork 54
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
Report L1 base fee set by L2s #111
Conversation
87eee3c
to
27d328d
Compare
|
||
for _, obs := range observations { | ||
if obs.SourceGasPriceUSD != nil { | ||
if obs.SourceGasPriceUSD.notNil() { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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"
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checks removed
LCOV of commit
|
@@ -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{}) |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
LCOV of commit
|
LCOV of commit
|
LCOV of commit
|
LCOV of commit
|
LCOV of commit
|
@@ -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. |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
for token, price := range obs.TokenPricesUSD { | ||
if price == nil { | ||
lggr.Warnw("Nil value in observed TokenPricesUSD", "token", token.Hex()) | ||
skipObservation = true |
There was a problem hiding this comment.
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
}
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
LCOV of commit
|
LCOV of commit
|
LCOV of commit
|
LCOV of commit
|
- 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 |
There was a problem hiding this comment.
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
* 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
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.