-
Notifications
You must be signed in to change notification settings - Fork 56
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
Changes from 10 commits
c35425e
0978d8f
27d328d
6fd2486
4288094
65ec92f
d68a786
fb7b0af
b2c5866
3985918
159cc13
bc94b46
78ab531
a6cd7c4
71992a3
436eef3
5e4ec20
9b52b04
3367405
5fb58a2
4bfb882
d3e7142
37badf1
76aa3ef
3d9d6ba
3237406
337a715
9717f92
d795be9
c4f9718
f619ede
42fa3bd
9d7673f
db7be89
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,11 +48,16 @@ var ( | |
_ types.ReportingPlugin = &CommitReportingPlugin{} | ||
) | ||
|
||
type update struct { | ||
type tokenPriceUpdate struct { | ||
timestamp time.Time | ||
value *big.Int | ||
} | ||
|
||
type gasPriceUpdate struct { | ||
timestamp time.Time | ||
value GasPrice | ||
} | ||
|
||
type CommitPluginConfig struct { | ||
lggr logger.Logger | ||
sourceLP, destLP logpoller.LogPoller | ||
|
@@ -68,6 +73,7 @@ type CommitPluginConfig struct { | |
sourceClient, destClient evmclient.Client | ||
leafHasher hashlib.LeafHasherInterface[[32]byte] | ||
checkFinalityTags bool | ||
commitStoreVersion string | ||
} | ||
|
||
type CommitReportingPlugin struct { | ||
|
@@ -104,7 +110,7 @@ func (rf *CommitReportingPluginFactory) NewReportingPlugin(config types.Reportin | |
if err != nil { | ||
return nil, types.ReportingPluginInfo{}, err | ||
} | ||
offchainConfig, err := ccipconfig.DecodeOffchainConfig[ccipconfig.CommitOffchainConfig](config.OffchainConfig) | ||
offchainConfig, err := DecodeCommitStoreOffchainConfig(rf.config.commitStoreVersion, config.OffchainConfig) | ||
if err != nil { | ||
return nil, types.ReportingPluginInfo{}, err | ||
} | ||
|
@@ -296,7 +302,7 @@ func (r *CommitReportingPlugin) generatePriceUpdates( | |
ctx context.Context, | ||
lggr logger.Logger, | ||
tokenDecimals map[common.Address]uint8, | ||
) (sourceGasPriceUSD *big.Int, tokenPricesUSD map[common.Address]*big.Int, err error) { | ||
) (sourceGasPriceUSD GasPrice, tokenPricesUSD map[common.Address]*big.Int, err error) { | ||
tokensWithDecimal := make([]common.Address, 0, len(tokenDecimals)) | ||
for token := range tokenDecimals { | ||
tokensWithDecimal = append(tokensWithDecimal, token) | ||
|
@@ -309,20 +315,20 @@ func (r *CommitReportingPlugin) generatePriceUpdates( | |
|
||
rawTokenPricesUSD, err := r.config.priceGetter.TokenPricesUSD(ctx, queryTokens) | ||
if err != nil { | ||
return nil, nil, err | ||
return GasPrice{}, nil, err | ||
} | ||
lggr.Infow("Raw token prices", "rawTokenPrices", rawTokenPricesUSD) | ||
|
||
// make sure that we got prices for all the tokens of our query | ||
for _, token := range queryTokens { | ||
if rawTokenPricesUSD[token] == nil { | ||
return nil, nil, errors.Errorf("missing token price: %+v", token) | ||
return GasPrice{}, nil, errors.Errorf("missing token price: %+v", token) | ||
} | ||
} | ||
|
||
sourceNativePriceUSD, exists := rawTokenPricesUSD[r.config.sourceNative] | ||
if !exists { | ||
return nil, nil, fmt.Errorf("missing source native (%s) price", r.config.sourceNative) | ||
return GasPrice{}, nil, fmt.Errorf("missing source native (%s) price", r.config.sourceNative) | ||
} | ||
|
||
tokenPricesUSD = make(map[common.Address]*big.Int, len(rawTokenPricesUSD)) | ||
|
@@ -339,17 +345,35 @@ func (r *CommitReportingPlugin) generatePriceUpdates( | |
// Observe a source chain price for pricing. | ||
sourceGasPriceWei, _, err := r.config.sourceFeeEstimator.GetFee(ctx, nil, 0, assets.NewWei(big.NewInt(int64(r.offchainConfig.MaxGasPrice)))) | ||
if err != nil { | ||
return nil, nil, err | ||
return GasPrice{}, nil, err | ||
} | ||
// Use legacy if no dynamic is available. | ||
gasPrice := sourceGasPriceWei.Legacy.ToInt() | ||
if sourceGasPriceWei.DynamicFeeCap != nil { | ||
gasPrice = sourceGasPriceWei.DynamicFeeCap.ToInt() | ||
} | ||
if gasPrice == nil { | ||
return nil, nil, fmt.Errorf("missing gas price %+v", sourceGasPriceWei) | ||
return GasPrice{}, nil, fmt.Errorf("missing gas price %+v", sourceGasPriceWei) | ||
} | ||
|
||
sourceNativeGasPriceUSD := calculateUsdPerUnitGas(gasPrice, sourceNativePriceUSD) | ||
sourceGasPriceUSD = GasPrice{ | ||
DAGasPrice: big.NewInt(0), | ||
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 commentThe 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:
When we instantiate the plugin:
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 l1Oracle := r.config.sourceFeeEstimator.L1Oracle(); l1Oracle != nil { | ||
l1GasPriceWei, err := l1Oracle.GasPrice(ctx) | ||
if err != nil { | ||
return GasPrice{}, nil, err | ||
} | ||
|
||
if l1GasPrice := l1GasPriceWei.ToInt(); l1GasPrice.Cmp(big.NewInt(0)) > 0 { | ||
// This assumes l1GasPrice is priced using the same native token as l2 native | ||
sourceGasPriceUSD.DAGasPrice = calculateUsdPerUnitGas(l1GasPrice, sourceNativePriceUSD) | ||
} | ||
} | ||
sourceGasPriceUSD = calculateUsdPerUnitGas(gasPrice, sourceNativePriceUSD) | ||
|
||
lggr.Infow("Observing gas price", "observedGasPriceWei", gasPrice, "observedGasPriceUSD", sourceGasPriceUSD) | ||
lggr.Infow("Observing token prices", "tokenPrices", tokenPricesUSD, "sourceNativePriceUSD", sourceNativePriceUSD) | ||
|
@@ -366,24 +390,24 @@ func calculateUsdPer1e18TokenAmount(price *big.Int, decimals uint8) *big.Int { | |
|
||
// Gets the latest token price updates based on logs within the heartbeat | ||
// The updates returned by this function are guaranteed to not contain nil values. | ||
func (r *CommitReportingPlugin) getLatestTokenPriceUpdates(ctx context.Context, now time.Time, checkInflight bool) (map[common.Address]update, error) { | ||
func (r *CommitReportingPlugin) getLatestTokenPriceUpdates(ctx context.Context, now time.Time, checkInflight bool) (map[common.Address]tokenPriceUpdate, error) { | ||
tokenPriceUpdates, err := r.config.destReader.GetTokenPriceUpdatesCreatedAfter( | ||
ctx, | ||
r.destPriceRegistry.Address(), | ||
now.Add(-r.offchainConfig.FeeUpdateHeartBeat.Duration()), | ||
now.Add(-r.offchainConfig.TokenPriceHeartBeat.Duration()), | ||
0, | ||
) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
latestUpdates := make(map[common.Address]update) | ||
for _, tokenPriceUpdate := range tokenPriceUpdates { | ||
priceUpdate := tokenPriceUpdate.Data | ||
latestUpdates := make(map[common.Address]tokenPriceUpdate) | ||
for _, tokenUpdate := range tokenPriceUpdates { | ||
priceUpdate := tokenUpdate.Data | ||
// Ordered by ascending timestamps | ||
timestamp := time.Unix(priceUpdate.Timestamp.Int64(), 0) | ||
if priceUpdate.Value != nil && !timestamp.Before(latestUpdates[priceUpdate.Token].timestamp) { | ||
latestUpdates[priceUpdate.Token] = update{ | ||
latestUpdates[priceUpdate.Token] = tokenPriceUpdate{ | ||
timestamp: timestamp, | ||
value: priceUpdate.Value, | ||
} | ||
|
@@ -407,18 +431,18 @@ func (r *CommitReportingPlugin) getLatestTokenPriceUpdates(ctx context.Context, | |
} | ||
|
||
// 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) { | ||
func (r *CommitReportingPlugin) getLatestGasPriceUpdate(ctx context.Context, now time.Time, checkInflight bool) (gasUpdate gasPriceUpdate, error error) { | ||
if checkInflight { | ||
latestInflightGasPriceUpdate := r.inflightReports.getLatestInflightGasPriceUpdate() | ||
if latestInflightGasPriceUpdate != nil && latestInflightGasPriceUpdate.timestamp.After(gasPriceUpdate.timestamp) { | ||
gasPriceUpdate = *latestInflightGasPriceUpdate | ||
if latestInflightGasPriceUpdate != nil && latestInflightGasPriceUpdate.timestamp.After(gasUpdate.timestamp) { | ||
gasUpdate = *latestInflightGasPriceUpdate | ||
} | ||
|
||
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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. Can change getLatestInflightGasPriceUpdate to return (update, bool) as opposed to *update. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Checks removed |
||
r.lggr.Infow("Latest gas price from inflight", "gasPriceUpdateVal", gasUpdate.value, "gasPriceUpdateTs", gasUpdate.timestamp) | ||
// Gas price can fluctuate frequently, many updates may be in flight. | ||
// If there is gas price update inflight, use it as source of truth, no need to check onchain. | ||
return gasPriceUpdate, nil | ||
return gasUpdate, nil | ||
} | ||
} | ||
|
||
|
@@ -427,29 +451,29 @@ func (r *CommitReportingPlugin) getLatestGasPriceUpdate(ctx context.Context, now | |
ctx, | ||
r.destPriceRegistry.Address(), | ||
r.config.sourceChainSelector, | ||
now.Add(-r.offchainConfig.FeeUpdateHeartBeat.Duration()), | ||
now.Add(-r.offchainConfig.GasPriceHeartBeat.Duration()), | ||
0, | ||
) | ||
if err != nil { | ||
return update{}, err | ||
return gasPriceUpdate{}, err | ||
} | ||
|
||
for _, priceUpdate := range gasPriceUpdates { | ||
// Ordered by ascending timestamps | ||
timestamp := time.Unix(priceUpdate.Data.Timestamp.Int64(), 0) | ||
if !timestamp.Before(gasPriceUpdate.timestamp) { | ||
gasPriceUpdate = update{ | ||
if !timestamp.Before(gasUpdate.timestamp) { | ||
gasUpdate = gasPriceUpdate{ | ||
timestamp: timestamp, | ||
value: priceUpdate.Data.Value, | ||
value: parseEncodedGasPrice(priceUpdate.Data.Value), | ||
} | ||
} | ||
} | ||
|
||
if gasPriceUpdate.value != nil { | ||
r.lggr.Infow("Latest gas price from log poller", "gasPriceUpdateVal", gasPriceUpdate.value, "gasPriceUpdateTs", gasPriceUpdate.timestamp) | ||
if gasUpdate.value.notNil() { | ||
r.lggr.Infow("Latest gas price from log poller", "gasPriceUpdateVal", gasUpdate.value, "gasPriceUpdateTs", gasUpdate.timestamp) | ||
} | ||
|
||
return gasPriceUpdate, nil | ||
return gasUpdate, nil | ||
} | ||
|
||
func (r *CommitReportingPlugin) Report(ctx context.Context, epochAndRound types.ReportTimestamp, _ types.Query, observations []types.AttributedObservation) (bool, types.Report, error) { | ||
|
@@ -572,12 +596,12 @@ func calculateIntervalConsensus(intervals []commit_store.CommitStoreInterval, f | |
|
||
// Note priceUpdates must be deterministic. | ||
// The provided latestTokenPrices should not contain nil values. | ||
func (r *CommitReportingPlugin) calculatePriceUpdates(observations []CommitObservation, latestGasPrice update, latestTokenPrices map[common.Address]update) commit_store.InternalPriceUpdates { | ||
func (r *CommitReportingPlugin) calculatePriceUpdates(observations []CommitObservation, latestGasPrice gasPriceUpdate, latestTokenPrices map[common.Address]tokenPriceUpdate) commit_store.InternalPriceUpdates { | ||
priceObservations := make(map[common.Address][]*big.Int) | ||
var sourceGasObservations []*big.Int | ||
var sourceGasObservations []GasPrice | ||
|
||
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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. seems tricky, since There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
// Add only non-nil source gas price | ||
sourceGasObservations = append(sourceGasObservations, obs.SourceGasPriceUSD) | ||
} | ||
|
@@ -600,8 +624,8 @@ func (r *CommitReportingPlugin) calculatePriceUpdates(observations []CommitObser | |
|
||
latestTokenPrice, exists := latestTokenPrices[token] | ||
if exists { | ||
tokenPriceUpdatedRecently := time.Since(latestTokenPrice.timestamp) < r.offchainConfig.FeeUpdateHeartBeat.Duration() | ||
tokenPriceNotChanged := !deviates(medianPrice, latestTokenPrice.value, int64(r.offchainConfig.FeeUpdateDeviationPPB)) | ||
tokenPriceUpdatedRecently := time.Since(latestTokenPrice.timestamp) < r.offchainConfig.TokenPriceHeartBeat.Duration() | ||
tokenPriceNotChanged := !deviates(medianPrice, latestTokenPrice.value, int64(r.offchainConfig.TokenPriceDeviationPPB)) | ||
if tokenPriceUpdatedRecently && tokenPriceNotChanged { | ||
r.lggr.Debugw("price was updated recently, skipping the update", | ||
"token", token, "newPrice", medianPrice, "existingPrice", latestTokenPrice.value) | ||
|
@@ -620,18 +644,32 @@ func (r *CommitReportingPlugin) calculatePriceUpdates(observations []CommitObser | |
return bytes.Compare(tokenPriceUpdates[i].SourceToken[:], tokenPriceUpdates[j].SourceToken[:]) == -1 | ||
}) | ||
|
||
usdPerUnitGas := big.NewInt(0) | ||
newGasPrice := GasPrice{} | ||
destChainSelector := uint64(0) | ||
|
||
if len(sourceGasObservations) > r.F { | ||
usdPerUnitGas = median(sourceGasObservations) // Compute the median price | ||
var daGasPrices []*big.Int | ||
var nativeGasPrices []*big.Int | ||
for _, o := range sourceGasObservations { | ||
daGasPrices = append(daGasPrices, o.DAGasPrice) | ||
nativeGasPrices = append(nativeGasPrices, o.NativeGasPrice) | ||
} | ||
newGasPrice = GasPrice{ | ||
DAGasPrice: median(daGasPrices), // Compute the median data availability gas price | ||
NativeGasPrice: median(nativeGasPrices), // Compute the median nativ gas price | ||
} | ||
|
||
destChainSelector = r.config.sourceChainSelector // Assuming plugin lane is A->B, we write to B the gas price of A | ||
|
||
if latestGasPrice.value != nil { | ||
gasPriceUpdatedRecently := time.Since(latestGasPrice.timestamp) < r.offchainConfig.FeeUpdateHeartBeat.Duration() | ||
gasPriceNotChanged := !deviates(usdPerUnitGas, latestGasPrice.value, int64(r.offchainConfig.FeeUpdateDeviationPPB)) | ||
if latestGasPrice.value.notNil() { | ||
gasPriceUpdatedRecently := time.Since(latestGasPrice.timestamp) < r.offchainConfig.GasPriceHeartBeat.Duration() | ||
gasPriceNotChanged := !deviates(newGasPrice.DAGasPrice, latestGasPrice.value.DAGasPrice, int64(r.offchainConfig.DAGasPriceDeviationPPB)) && !deviates(newGasPrice.NativeGasPrice, latestGasPrice.value.NativeGasPrice, int64(r.offchainConfig.NativeGasPriceDeviationPPB)) | ||
|
||
if gasPriceUpdatedRecently && gasPriceNotChanged { | ||
usdPerUnitGas = big.NewInt(0) | ||
newGasPrice = GasPrice{ | ||
DAGasPrice: big.NewInt(0), | ||
NativeGasPrice: big.NewInt(0), | ||
} | ||
destChainSelector = uint64(0) | ||
} | ||
} | ||
|
@@ -640,7 +678,7 @@ func (r *CommitReportingPlugin) calculatePriceUpdates(observations []CommitObser | |
return commit_store.InternalPriceUpdates{ | ||
TokenPriceUpdates: tokenPriceUpdates, | ||
DestChainSelector: destChainSelector, | ||
UsdPerUnitGas: usdPerUnitGas, // we MUST pass zero to skip the update (never nil) | ||
UsdPerUnitGas: newGasPrice.encode(), // we MUST pass zero to skip the update (never nil) | ||
} | ||
} | ||
|
||
|
@@ -818,17 +856,22 @@ func (r *CommitReportingPlugin) isStaleMerkleRoot(ctx context.Context, lggr logg | |
} | ||
|
||
func (r *CommitReportingPlugin) isStaleGasPrice(ctx context.Context, lggr logger.Logger, priceUpdates commit_store.InternalPriceUpdates, checkInflight bool) bool { | ||
gasPriceUpdate, err := r.getLatestGasPriceUpdate(ctx, time.Now(), checkInflight) | ||
latestGasPrice, err := r.getLatestGasPriceUpdate(ctx, time.Now(), checkInflight) | ||
if err != nil { | ||
return true | ||
} | ||
|
||
if gasPriceUpdate.value != nil && !deviates(priceUpdates.UsdPerUnitGas, gasPriceUpdate.value, int64(r.offchainConfig.FeeUpdateDeviationPPB)) { | ||
lggr.Infow("Report is stale because of gas price", | ||
"latestGasPriceUpdate", gasPriceUpdate.value, | ||
"usdPerUnitGas", priceUpdates.UsdPerUnitGas, | ||
"destChainSelector", priceUpdates.DestChainSelector) | ||
return true | ||
if latestGasPrice.value.notNil() { | ||
newGasPrice := parseEncodedGasPrice(priceUpdates.UsdPerUnitGas) | ||
gasPriceNotChanged := !deviates(newGasPrice.DAGasPrice, latestGasPrice.value.NativeGasPrice, int64(r.offchainConfig.DAGasPriceDeviationPPB)) && !deviates(newGasPrice.NativeGasPrice, latestGasPrice.value.NativeGasPrice, int64(r.offchainConfig.NativeGasPriceDeviationPPB)) | ||
|
||
if gasPriceNotChanged { | ||
lggr.Infow("Report is stale because of gas price", | ||
"latestGasPriceUpdate", latestGasPrice.value, | ||
"usdPerUnitGas", priceUpdates.UsdPerUnitGas, | ||
"destChainSelector", priceUpdates.DestChainSelector) | ||
return true | ||
} | ||
} | ||
|
||
return false | ||
|
@@ -844,7 +887,7 @@ func (r *CommitReportingPlugin) isStaleTokenPrices(ctx context.Context, lggr log | |
|
||
for _, tokenUpdate := range priceUpdates { | ||
latestUpdate, ok := latestTokenPriceUpdates[tokenUpdate.SourceToken] | ||
priceEqual := ok && !deviates(tokenUpdate.UsdPerToken, latestUpdate.value, int64(r.offchainConfig.FeeUpdateDeviationPPB)) | ||
priceEqual := ok && !deviates(tokenUpdate.UsdPerToken, latestUpdate.value, int64(r.offchainConfig.TokenPriceDeviationPPB)) | ||
|
||
if !priceEqual { | ||
lggr.Infow("Found non-stale token price", "token", tokenUpdate.SourceToken, "usdPerToken", tokenUpdate.UsdPerToken, "latestUpdate", latestUpdate.value) | ||
|
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