From c51c0b8ba03c22790e42358a98d974b3a0036a39 Mon Sep 17 00:00:00 2001 From: Bartek Tofel Date: Tue, 3 Sep 2024 10:47:45 +0200 Subject: [PATCH] allow to disable congestion metrics, when using automated gas estimations; update readme --- seth/README.md | 19 +++--- seth/client.go | 2 +- seth/config.go | 2 +- seth/config_test.go | 2 +- seth/gas.go | 7 +++ seth/gas_adjuster.go | 138 ++++++++++++++++++++++--------------------- 6 files changed, 90 insertions(+), 80 deletions(-) diff --git a/seth/README.md b/seth/README.md index f93719836..51b55eba8 100644 --- a/seth/README.md +++ b/seth/README.md @@ -226,7 +226,7 @@ client, err := builder. // EIP-1559 and gas estimations WithEIP1559DynamicFees(true). WithDynamicGasPrices(120_000_000_000, 44_000_000_000). - WithGasPriceEstimations(false, 10, seth.Priority_Fast). + WithGasPriceEstimations(true, 10, seth.Priority_Fast). // gas bumping: retries, max gas price, bumping strategy function WithGasBumping(5, 100_000_000_000, PriorityBasedGasBumpingStrategyFn). Build() @@ -400,20 +400,22 @@ For real networks, the estimation process differs for legacy transactions and th 1. **Initial Price**: Query the network node for the current suggested gas price. 2. **Priority Adjustment**: Modify the initial price based on `gas_price_estimation_tx_priority`. Higher priority increases the price to ensure faster inclusion in a block. -3. **Congestion Analysis**: Examine the last X blocks (as specified by `gas_price_estimation_blocks`) to determine network congestion, calculating the usage rate of gas in each block and giving recent blocks more weight. +3. **Congestion Analysis**: Examine the last X blocks (as specified by `gas_price_estimation_blocks`) to determine network congestion, calculating the usage rate of gas in each block and giving recent blocks more weight. Disabled if `gas_price_estimation_blocks` equals `0`. 4. **Buffering**: Add a buffer to the adjusted gas price to increase transaction reliability during high congestion. ##### EIP-1559 Transactions 1. **Tip Fee Query**: Ask the node for the current recommended tip fee. 2. **Fee History Analysis**: Gather the base fee and tip history from recent blocks to establish a fee baseline. -3. **Fee Selection**: Use the greater of the node's suggested tip or the historical average tip for upcoming calculations. +3. **Fee Selection**: Use the greatest of the node's suggested tip or the historical average tip for upcoming calculations. 4. **Priority and Adjustment**: Increase the base and tip fees based on transaction priority (`gas_price_estimation_tx_priority`), which influences how much you are willing to spend to expedite your transaction. 5. **Final Fee Calculation**: Sum the base fee and adjusted tip to set the `gas_fee_cap`. 6. **Congestion Buffer**: Similar to legacy transactions, analyze congestion and apply a buffer to both the fee cap and the tip to secure transaction inclusion. Understanding and setting these parameters correctly ensures that your transactions are processed efficiently and cost-effectively on the network. +When fetching historical base fee and tip data, we will use the last `gas_price_estimation_blocks` blocks. If it's set to `0` we will default to `100` last blocks. If the blockchain has less than `100` blocks we will use all of them. + Finally, `gas_price_estimation_tx_priority` is also used, when deciding, which percentile to use for base fee and tip for historical fee data. Here's how that looks: ```go @@ -445,7 +447,7 @@ For fast transactions we will increase gas price by 20%, for standard we will us ##### Buffer percents -We further adjust the gas price by adding a buffer to it, based on congestion rate: +If `gas_price_estimation_blocks` is higher than `0` we further adjust the gas price by adding a buffer to it, based on congestion rate: ```go case Congestion_Low: @@ -458,13 +460,8 @@ case Congestion_VeryHigh: return 1.40, nil ``` -For low congestion rate we will increase gas price by 10%, for medium by 20%, for high by 30% and for very high by 40%. - -We cache block header data in an in-memory cache, so we don't have to fetch it every time we estimate gas. The cache has capacity equal to `gas_price_estimation_blocks` and every time we add a new element, we remove one that is least frequently used and oldest (with block number being a constant and chain always moving forward it makes no sense to keep old blocks). - -It's important to know that in order to use congestion metrics we need to fetch at least 80% of the requested blocks. If that fails, we will skip this part of the estimation and only adjust the gas price based on priority. - -For both transaction types if any of the steps fails, we fallback to hardcoded values. +For low congestion rate we will increase gas price by 10%, for medium by 20%, for high by 30% and for very high by 40%. We cache block header data in an in-memory cache, so we don't have to fetch it every time we estimate gas. The cache has capacity equal to `gas_price_estimation_blocks` and every time we add a new element, we remove one that is least frequently used and oldest (with block number being a constant and chain always moving forward it makes no sense to keep old blocks). It's important to know that in order to use congestion metrics we need to fetch at least 80% of the requested blocks. If that fails, we will skip this part of the estimation and only adjust the gas price based on priority. +For both transaction types if any of the steps fails, we fall back to hardcoded values. ### DOT graphs diff --git a/seth/client.go b/seth/client.go index 857886e3c..ace2967fc 100644 --- a/seth/client.go +++ b/seth/client.go @@ -151,7 +151,7 @@ func NewClientWithConfig(cfg *Config) (*Client, error) { func ValidateConfig(cfg *Config) error { if cfg.Network.GasPriceEstimationEnabled { if cfg.Network.GasPriceEstimationBlocks == 0 { - return errors.New("when automating gas estimation is enabled blocks must be greater than 0. fix it or disable gas estimation") + L.Debug().Msg("Gas estimation is enabled, but block headers to use is set to 0. Will not use block congestion for gas estimation") } cfg.Network.GasPriceEstimationTxPriority = strings.ToLower(cfg.Network.GasPriceEstimationTxPriority) diff --git a/seth/config.go b/seth/config.go index cb335f8d0..dbb6b7dad 100644 --- a/seth/config.go +++ b/seth/config.go @@ -34,7 +34,7 @@ const ( DefaultPendingNonceProtectionTimeout = 1 * time.Minute DefaultTransferGasFee = 21_000 - DefaultGasPrice = 1_000_000_000 // 1 Gwei + DefaultGasPrice = 100_000_000_000 // 100 Gwei DefaultGasFeeCap = 100_000_000_000 // 100 Gwei DefaultGasTipCap = 50_000_000_000 // 50 Gwei ) diff --git a/seth/config_test.go b/seth/config_test.go index 8b316dfc2..9f1b92da7 100644 --- a/seth/config_test.go +++ b/seth/config_test.go @@ -125,7 +125,7 @@ func TestConfig_Eip1559Gas_With_Estimations(t *testing.T) { // Gas price and estimations WithEIP1559DynamicFees(true). WithDynamicGasPrices(120_000_000_000, 44_000_000_000). - WithGasPriceEstimations(false, 10, seth.Priority_Fast). + WithGasPriceEstimations(true, 10, seth.Priority_Fast). Build() require.NoError(t, err, "failed to build client") diff --git a/seth/gas.go b/seth/gas.go index 10307ecf7..b2979686f 100644 --- a/seth/gas.go +++ b/seth/gas.go @@ -25,6 +25,13 @@ func (m *GasEstimator) Stats(fromNumber uint64, priorityPerc float64) (GasSugges if err != nil { return GasSuggestions{}, err } + if fromNumber == 0 { + if bn > 100 { + fromNumber = bn - 100 + } else { + fromNumber = 1 + } + } hist, err := m.Client.Client.FeeHistory(context.Background(), fromNumber, big.NewInt(int64(bn)), []float64{priorityPerc}) if err != nil { return GasSuggestions{}, err diff --git a/seth/gas_adjuster.go b/seth/gas_adjuster.go index 1e6a6cbe9..b9b891d43 100644 --- a/seth/gas_adjuster.go +++ b/seth/gas_adjuster.go @@ -265,44 +265,47 @@ func (m *Client) GetSuggestedEIP1559Fees(ctx context.Context, priority string) ( initialFeeCap := new(big.Int).Add(big.NewInt(int64(baseFee64)), currentGasTip) - // between 0 and 1 (empty blocks - full blocks) - var congestionMetric float64 - //nolint - congestionMetric, err = m.CalculateNetworkCongestionMetric(m.Cfg.Network.GasPriceEstimationBlocks, CongestionStrategy_NewestFirst) - if err == nil { - congestionClassification := classifyCongestion(congestionMetric) - - L.Debug(). - Str("CongestionMetric", fmt.Sprintf("%.4f", congestionMetric)). - Str("CongestionClassification", congestionClassification). - Float64("AdjustmentFactor", adjustmentFactor). - Str("Priority", priority). - Msg("Adjustment factors") + // skip if we do not want to calculate congestion metrics + if m.Cfg.Network.GasPriceEstimationBlocks > 0 { + // between 0 and 1 (empty blocks - full blocks) + var congestionMetric float64 + //nolint + congestionMetric, err = m.CalculateNetworkCongestionMetric(m.Cfg.Network.GasPriceEstimationBlocks, CongestionStrategy_NewestFirst) + if err == nil { + congestionClassification := classifyCongestion(congestionMetric) + + L.Debug(). + Str("CongestionMetric", fmt.Sprintf("%.4f", congestionMetric)). + Str("CongestionClassification", congestionClassification). + Float64("AdjustmentFactor", adjustmentFactor). + Str("Priority", priority). + Msg("Adjustment factors") + + // between 1.1 and 1.4 + var bufferAdjustment float64 + bufferAdjustment, err = getCongestionFactor(congestionClassification) + if err != nil { + return + } - // between 1.1 and 1.4 - var bufferAdjustment float64 - bufferAdjustment, err = getCongestionFactor(congestionClassification) - if err != nil { + // Calculate base fee buffer + bufferedBaseFeeFloat := new(big.Float).Mul(new(big.Float).SetInt(adjustedBaseFee), big.NewFloat(bufferAdjustment)) + adjustedBaseFee, _ = bufferedBaseFeeFloat.Int(nil) + + // Apply buffer also to the tip + bufferedTipCapFloat := new(big.Float).Mul(new(big.Float).SetInt(adjustedTipCap), big.NewFloat(bufferAdjustment)) + adjustedTipCap, _ = bufferedTipCapFloat.Int(nil) + } else if !strings.Contains(err.Error(), BlockFetchingErr) { return + } else { + L.Warn(). + Err(err). + Msg("Failed to calculate congestion metric. Skipping congestion buffer adjustment") + + // set error to nil, as we can still calculate the fees, but without congestion buffer + // we don't want to return an error in this case + err = nil } - - // Calculate base fee buffer - bufferedBaseFeeFloat := new(big.Float).Mul(new(big.Float).SetInt(adjustedBaseFee), big.NewFloat(bufferAdjustment)) - adjustedBaseFee, _ = bufferedBaseFeeFloat.Int(nil) - - // Apply buffer also to the tip - bufferedTipCapFloat := new(big.Float).Mul(new(big.Float).SetInt(adjustedTipCap), big.NewFloat(bufferAdjustment)) - adjustedTipCap, _ = bufferedTipCapFloat.Int(nil) - } else if !strings.Contains(err.Error(), BlockFetchingErr) { - return - } else { - L.Warn(). - Err(err). - Msg("Failed to calculate congestion metric. Skipping congestion buffer adjustment") - - // set error to nil, as we can still calculate the fees, but without congestion buffer - // we don't want to return an error in this case - err = nil } maxFeeCap = new(big.Int).Add(adjustedBaseFee, adjustedTipCap) @@ -366,40 +369,43 @@ func (m *Client) GetSuggestedLegacyFees(ctx context.Context, priority string) (a adjustedGasPriceFloat := new(big.Float).Mul(big.NewFloat(adjustmentFactor), new(big.Float).SetFloat64(float64(suggestedGasPrice.Int64()))) adjustedGasPrice, _ = adjustedGasPriceFloat.Int(nil) - // between 0 and 1 (empty blocks - full blocks) - var congestionMetric float64 - //nolint - congestionMetric, err = m.CalculateNetworkCongestionMetric(m.Cfg.Network.GasPriceEstimationBlocks, CongestionStrategy_NewestFirst) - if err == nil { - congestionClassification := classifyCongestion(congestionMetric) - - L.Debug(). - Str("CongestionMetric", fmt.Sprintf("%.4f", congestionMetric)). - Str("CongestionClassification", congestionClassification). - Float64("AdjustmentFactor", adjustmentFactor). - Str("Priority", priority). - Msg("Suggested Legacy fees") + // skip if we do not want to calculate congestion metrics + if m.Cfg.Network.GasPriceEstimationBlocks > 0 { + // between 0 and 1 (empty blocks - full blocks) + var congestionMetric float64 + //nolint + congestionMetric, err = m.CalculateNetworkCongestionMetric(m.Cfg.Network.GasPriceEstimationBlocks, CongestionStrategy_NewestFirst) + if err == nil { + congestionClassification := classifyCongestion(congestionMetric) + + L.Debug(). + Str("CongestionMetric", fmt.Sprintf("%.4f", congestionMetric)). + Str("CongestionClassification", congestionClassification). + Float64("AdjustmentFactor", adjustmentFactor). + Str("Priority", priority). + Msg("Suggested Legacy fees") + + // between 1.1 and 1.4 + var bufferAdjustment float64 + bufferAdjustment, err = getCongestionFactor(congestionClassification) + if err != nil { + return + } - // between 1.1 and 1.4 - var bufferAdjustment float64 - bufferAdjustment, err = getCongestionFactor(congestionClassification) - if err != nil { + // Calculate and apply the buffer. + bufferedGasPriceFloat := new(big.Float).Mul(new(big.Float).SetInt(adjustedGasPrice), big.NewFloat(bufferAdjustment)) + adjustedGasPrice, _ = bufferedGasPriceFloat.Int(nil) + } else if !strings.Contains(err.Error(), BlockFetchingErr) { return + } else { + L.Warn(). + Err(err). + Msg("Failed to calculate congestion metric. Skipping congestion buffer adjustment") + + // set error to nil, as we can still calculate the fees, but without congestion buffer + // we don't want to return an error in this case + err = nil } - - // Calculate and apply the buffer. - bufferedGasPriceFloat := new(big.Float).Mul(new(big.Float).SetInt(adjustedGasPrice), big.NewFloat(bufferAdjustment)) - adjustedGasPrice, _ = bufferedGasPriceFloat.Int(nil) - } else if !strings.Contains(err.Error(), BlockFetchingErr) { - return - } else { - L.Warn(). - Err(err). - Msg("Failed to calculate congestion metric. Skipping congestion buffer adjustment") - - // set error to nil, as we can still calculate the fees, but without congestion buffer - // we don't want to return an error in this case - err = nil } L.Debug().