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

Refactor Fixed Price estimator #13834

Draft
wants to merge 20 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/breezy-eggs-glow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": minor
---

Refactor Fixed Price estimator #internal
51 changes: 1 addition & 50 deletions common/fee/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@ package fee

import (
"errors"
"fmt"
"math/big"

"github.com/smartcontractkit/chainlink-common/pkg/chains/label"
"github.com/smartcontractkit/chainlink-common/pkg/logger"
bigmath "github.com/smartcontractkit/chainlink-common/pkg/utils/big_math"
)

Expand All @@ -30,56 +27,10 @@ func CalculateFee(
return bigmath.Min(defaultPrice, maxFeePriceAllowed)
}

// CalculateBumpedFee computes the next fee price to attempt as the largest of:
// - A configured percentage bump (bumpPercent) on top of the baseline price.
// - A configured fixed amount of Unit (bumpMin) on top of the baseline price.
// The baseline price is the maximum of the previous fee price attempt and the node's current fee price.
func CalculateBumpedFee(
lggr logger.SugaredLogger,
currentfeePrice, originalfeePrice, maxFeePriceInput, maxBumpPrice, bumpMin *big.Int,
bumpPercent uint16,
toChainUnit feeUnitToChainUnit,
) (*big.Int, error) {
maxFeePrice := bigmath.Min(maxFeePriceInput, maxBumpPrice)
bumpedFeePrice := MaxBumpedFee(originalfeePrice, bumpPercent, bumpMin)

// Update bumpedFeePrice if currentfeePrice is higher than bumpedFeePrice and within maxFeePrice
bumpedFeePrice = maxFee(lggr, currentfeePrice, bumpedFeePrice, maxFeePrice, "fee price", toChainUnit)

if bumpedFeePrice.Cmp(maxFeePrice) > 0 {
return maxFeePrice, fmt.Errorf("bumped fee price of %s would exceed configured max fee price of %s (original price was %s). %s: %w",
toChainUnit(bumpedFeePrice), toChainUnit(maxFeePrice), toChainUnit(originalfeePrice), label.NodeConnectivityProblemWarning, ErrBumpFeeExceedsLimit)
} else if bumpedFeePrice.Cmp(originalfeePrice) == 0 {
// NOTE: This really shouldn't happen since we enforce minimums for
// FeeEstimator.BumpPercent and FeeEstimator.BumpMin in the config validation,
// but it's here anyway for a "belts and braces" approach
return bumpedFeePrice, fmt.Errorf("bumped fee price of %s is equal to original fee price of %s."+
" ACTION REQUIRED: This is a configuration error, you must increase either "+
"FeeEstimator.BumpPercent or FeeEstimator.BumpMin: %w", toChainUnit(bumpedFeePrice), toChainUnit(bumpedFeePrice), ErrBump)
}
return bumpedFeePrice, nil
}

// Returns highest bumped fee price of originalFeePrice bumped by fixed units or percentage.
func MaxBumpedFee(originalFeePrice *big.Int, feeBumpPercent uint16, feeBumpUnits *big.Int) *big.Int {
return bigmath.Max(
addPercentage(originalFeePrice, feeBumpPercent),
AddPercentage(originalFeePrice, feeBumpPercent),
new(big.Int).Add(originalFeePrice, feeBumpUnits),
)
}

// Returns the max of currentFeePrice, bumpedFeePrice, and maxFeePrice
func maxFee(lggr logger.SugaredLogger, currentFeePrice, bumpedFeePrice, maxFeePrice *big.Int, feeType string, toChainUnit feeUnitToChainUnit) *big.Int {
if currentFeePrice == nil {
return bumpedFeePrice
}
if currentFeePrice.Cmp(maxFeePrice) > 0 {
// Shouldn't happen because the estimator should not be allowed to
// estimate a higher fee than the maximum allowed
lggr.AssumptionViolationf("Ignoring current %s of %s that would exceed max %s of %s", feeType, toChainUnit(currentFeePrice), feeType, toChainUnit(maxFeePrice))
} else if bumpedFeePrice.Cmp(currentFeePrice) < 0 {
// If the current fee price is higher than the old price bumped, use that instead
return currentFeePrice
}
return bumpedFeePrice
}
5 changes: 1 addition & 4 deletions common/fee/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,9 @@ func ApplyMultiplier(feeLimit uint64, multiplier float32) (uint64, error) {
}

// Returns the input value increased by the given percentage.
func addPercentage(value *big.Int, percentage uint16) *big.Int {
func AddPercentage(value *big.Int, percentage uint16) *big.Int {
bumped := new(big.Int)
bumped.Mul(value, big.NewInt(int64(100+percentage)))
bumped.Div(bumped, big.NewInt(100))
return bumped
}

// Returns the fee in its chain specific unit.
type feeUnitToChainUnit func(fee *big.Int) string
4 changes: 0 additions & 4 deletions core/chains/evm/assets/wei.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,6 @@ func suffixExp(suf string) int32 {
// additional functions
type Wei ubig.Big

func MaxWei(w, x *Wei) *Wei {
return NewWei(bigmath.Max(w.ToInt(), x.ToInt()))
}

// NewWei constructs a Wei from *big.Int.
func NewWei(i *big.Int) *Wei {
return (*Wei)(i)
Expand Down
8 changes: 8 additions & 0 deletions core/chains/evm/client/chain_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,14 @@ func (c *chainClient) LatestFinalizedBlock(ctx context.Context) (*evmtypes.Head,
return c.multiNode.LatestFinalizedBlock(ctx)
}

func (c *chainClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) {
rpc, err := c.multiNode.SelectNodeRPC()
if err != nil {
return feeHistory, err
}
return rpc.FeeHistory(ctx, blockCount, rewardPercentiles)
}

func (c *chainClient) CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError {
msg := ethereum.CallMsg{
From: from,
Expand Down
30 changes: 30 additions & 0 deletions core/chains/evm/client/mocks/rpc_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions core/chains/evm/client/rpc_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ type RPCClient interface {
SuggestGasTipCap(ctx context.Context) (t *big.Int, err error)
TransactionReceiptGeth(ctx context.Context, txHash common.Hash) (r *types.Receipt, err error)
GetInterceptedChainInfo() (latest, highestUserObservations commonclient.ChainInfo)
FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error)
}

type rawclient struct {
Expand Down Expand Up @@ -473,6 +474,7 @@ func (r *rpcClient) TransactionReceiptGeth(ctx context.Context, txHash common.Ha

return
}

func (r *rpcClient) TransactionByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, err error) {
ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx)
defer cancel()
Expand Down Expand Up @@ -942,6 +944,29 @@ func (r *rpcClient) BalanceAt(ctx context.Context, account common.Address, block
return
}

func (r *rpcClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) {
ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx)
defer cancel()
lggr := r.newRqLggr().With("blockCount", blockCount, "rewardPercentiles", rewardPercentiles)

lggr.Debug("RPC call: evmclient.Client#FeeHistory")
start := time.Now()
if http != nil {
feeHistory, err = http.geth.FeeHistory(ctx, blockCount, nil, rewardPercentiles)
err = r.wrapHTTP(err)
} else {
feeHistory, err = ws.geth.FeeHistory(ctx, blockCount, nil, rewardPercentiles)
err = r.wrapWS(err)
}
duration := time.Since(start)

r.logResult(lggr, err, duration, r.getRPCDomain(), "FeeHistory",
"feeHistory", feeHistory,
)

return
}

// CallArgs represents the data used to call the balance method of a contract.
// "To" is the address of the ERC contract. "Data" is the message sent
// to the contract. "From" is the sender address.
Expand Down
5 changes: 3 additions & 2 deletions core/chains/evm/gas/block_history_estimator.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ type estimatorGasEstimatorConfig interface {
TipCapMin() *assets.Wei
PriceMax() *assets.Wei
PriceMin() *assets.Wei
bumpConfig
BumpPercent() uint16
BumpMin() *assets.Wei
}

//go:generate mockery --quiet --name Config --output ./mocks/ --case=underscore
Expand Down Expand Up @@ -409,7 +410,7 @@ func (b *BlockHistoryEstimator) GetDynamicFee(_ context.Context, maxGasPriceWei
"Using Evm.GasEstimator.TipCapDefault as fallback.", "blocks", b.getBlockHistoryNumbers())
tipCap = b.eConfig.TipCapDefault()
}
maxGasPrice := getMaxGasPrice(maxGasPriceWei, b.eConfig.PriceMax())
maxGasPrice := assets.WeiMin(maxGasPriceWei, b.eConfig.PriceMax())
if b.eConfig.BumpThreshold() == 0 {
// just use the max gas price if gas bumping is disabled
feeCap = maxGasPrice
Expand Down
Loading
Loading