Skip to content

Commit

Permalink
Merge pull request #1993 from OffchainLabs/editable-dataposter-maxfee…
Browse files Browse the repository at this point in the history
…cap-calculation

Make the formula to calculate max fee cap configurable and add time based factor to default formula
  • Loading branch information
ganeshvanahalli authored Dec 7, 2023
2 parents 8286b64 + 05eb4e1 commit 01f66c2
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 14 deletions.
66 changes: 52 additions & 14 deletions arbnode/dataposter/data_poster.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"sync"
"time"

"github.com/Knetic/govaluate"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
Expand Down Expand Up @@ -71,6 +72,8 @@ type DataPoster struct {
nonce uint64
queue QueueStorage
errorCount map[uint64]int // number of consecutive intermittent errors rbf-ing or sending, per nonce

maxFeeCapExpression *govaluate.EvaluableExpression
}

// signerFn is a signer function callback when a contract requires a method to
Expand Down Expand Up @@ -152,20 +155,25 @@ func NewDataPoster(ctx context.Context, opts *DataPosterOpts) (*DataPoster, erro
default:
queue = slice.NewStorage(func() storage.EncoderDecoderInterface { return &storage.EncoderDecoder{} })
}
expression, err := govaluate.NewEvaluableExpression(cfg.MaxFeeCapFormula)
if err != nil {
return nil, fmt.Errorf("error creating govaluate evaluable expression for calculating maxFeeCap: %w", err)
}
dp := &DataPoster{
headerReader: opts.HeaderReader,
client: opts.HeaderReader.Client(),
sender: opts.Auth.From,
signer: func(_ context.Context, addr common.Address, tx *types.Transaction) (*types.Transaction, error) {
return opts.Auth.Signer(addr, tx)
},
config: opts.Config,
usingNoOpStorage: useNoOpStorage,
replacementTimes: replacementTimes,
metadataRetriever: opts.MetadataRetriever,
queue: queue,
redisLock: opts.RedisLock,
errorCount: make(map[uint64]int),
config: opts.Config,
usingNoOpStorage: useNoOpStorage,
replacementTimes: replacementTimes,
metadataRetriever: opts.MetadataRetriever,
queue: queue,
redisLock: opts.RedisLock,
errorCount: make(map[uint64]int),
maxFeeCapExpression: expression,
}
if cfg.ExternalSigner.URL != "" {
signer, sender, err := externalSigner(ctx, &cfg.ExternalSigner)
Expand Down Expand Up @@ -350,6 +358,25 @@ func (p *DataPoster) GetNextNonceAndMeta(ctx context.Context) (uint64, []byte, e

const minRbfIncrease = arbmath.OneInBips * 11 / 10

// evalMaxFeeCapExpr uses MaxFeeCapFormula from config to calculate the expression's result by plugging in appropriate parameter values
func (p *DataPoster) evalMaxFeeCapExpr(backlogOfBatches uint64, elapsed time.Duration) (*big.Int, error) {
config := p.config()
parameters := map[string]any{
"BacklogOfBatches": float64(backlogOfBatches),
"UrgencyGWei": config.UrgencyGwei,
"ElapsedTime": float64(elapsed),
"ElapsedTimeBase": float64(config.ElapsedTimeBase),
"ElapsedTimeImportance": config.ElapsedTimeImportance,
"TargetPriceGWei": config.TargetPriceGwei,
"GWei": params.GWei,
}
result, err := p.maxFeeCapExpression.Evaluate(parameters)
if err != nil {
return nil, fmt.Errorf("error evaluating maxFeeCapExpression: %w", err)
}
return arbmath.FloatToBig(result.(float64)), nil
}

func (p *DataPoster) feeAndTipCaps(ctx context.Context, nonce uint64, gasLimit uint64, lastFeeCap *big.Int, lastTipCap *big.Int, dataCreatedAt time.Time, backlogOfBatches uint64) (*big.Int, *big.Int, error) {
config := p.config()
latestHeader, err := p.headerReader.LastHeader(ctx)
Expand Down Expand Up @@ -389,13 +416,10 @@ func (p *DataPoster) feeAndTipCaps(ctx context.Context, nonce uint64, gasLimit u
}

elapsed := time.Since(dataCreatedAt)
// MaxFeeCap = (BacklogOfBatches^2 * UrgencyGWei^2 + TargetPriceGWei) * GWei
maxFeeCap :=
arbmath.FloatToBig(
(float64(arbmath.SquareUint(backlogOfBatches))*
arbmath.SquareFloat(config.UrgencyGwei) +
config.TargetPriceGwei) *
params.GWei)
maxFeeCap, err := p.evalMaxFeeCapExpr(backlogOfBatches, elapsed)
if err != nil {
return nil, nil, err
}
if arbmath.BigGreaterThan(newFeeCap, maxFeeCap) {
log.Warn(
"reducing proposed fee cap to current maximum",
Expand Down Expand Up @@ -756,6 +780,9 @@ type DataPosterConfig struct {
LegacyStorageEncoding bool `koanf:"legacy-storage-encoding" reload:"hot"`
Dangerous DangerousConfig `koanf:"dangerous"`
ExternalSigner ExternalSignerCfg `koanf:"external-signer"`
MaxFeeCapFormula string `koanf:"max-fee-cap-formula" reload:"hot"`
ElapsedTimeBase time.Duration `koanf:"elapsed-time-base" reload:"hot"`
ElapsedTimeImportance float64 `koanf:"elapsed-time-importance" reload:"hot"`
}

type ExternalSignerCfg struct {
Expand Down Expand Up @@ -802,6 +829,11 @@ func DataPosterConfigAddOptions(prefix string, f *pflag.FlagSet, defaultDataPost
f.Bool(prefix+".use-db-storage", defaultDataPosterConfig.UseDBStorage, "uses database storage when enabled")
f.Bool(prefix+".use-noop-storage", defaultDataPosterConfig.UseNoOpStorage, "uses noop storage, it doesn't store anything")
f.Bool(prefix+".legacy-storage-encoding", defaultDataPosterConfig.LegacyStorageEncoding, "encodes items in a legacy way (as it was before dropping generics)")
f.String(prefix+".max-fee-cap-formula", defaultDataPosterConfig.MaxFeeCapFormula, "mathematical formula to calculate maximum fee cap the result of which would be float64.\n"+
"This expression is expected to be evaluated please refer https://github.com/Knetic/govaluate/blob/master/MANUAL.md to find all available mathematical operators.\n"+
"Currently available variables to construct the formula are BacklogOfBatches, UrgencyGWei, ElapsedTime, ElapsedTimeBase, ElapsedTimeImportance, TargetPriceGWei and GWei")
f.Duration(prefix+".elapsed-time-base", defaultDataPosterConfig.ElapsedTimeBase, "unit to measure the time elapsed since creation of transaction used for maximum fee cap calculation")
f.Float64(prefix+".elapsed-time-importance", defaultDataPosterConfig.ElapsedTimeImportance, "weight given to the units of time elapsed used for maximum fee cap calculation")

signature.SimpleHmacConfigAddOptions(prefix+".redis-signer", f)
addDangerousOptions(prefix+".dangerous", f)
Expand Down Expand Up @@ -836,6 +868,9 @@ var DefaultDataPosterConfig = DataPosterConfig{
LegacyStorageEncoding: false,
Dangerous: DangerousConfig{ClearDBStorage: false},
ExternalSigner: ExternalSignerCfg{Method: "eth_signTransaction"},
MaxFeeCapFormula: "(((BacklogOfBatches * UrgencyGWei) ** 2) + ((ElapsedTime/ElapsedTimeBase) ** 2) * ElapsedTimeImportance + TargetPriceGWei) * GWei",
ElapsedTimeBase: 10 * time.Minute,
ElapsedTimeImportance: 10,
}

var DefaultDataPosterConfigForValidator = func() DataPosterConfig {
Expand All @@ -859,6 +894,9 @@ var TestDataPosterConfig = DataPosterConfig{
UseNoOpStorage: false,
LegacyStorageEncoding: false,
ExternalSigner: ExternalSignerCfg{Method: "eth_signTransaction"},
MaxFeeCapFormula: "(((BacklogOfBatches * UrgencyGWei) ** 2) + ((ElapsedTime/ElapsedTimeBase) ** 2) * ElapsedTimeImportance + TargetPriceGWei) * GWei",
ElapsedTimeBase: 10 * time.Minute,
ElapsedTimeImportance: 10,
}

var TestDataPosterConfigForValidator = func() DataPosterConfig {
Expand Down
23 changes: 23 additions & 0 deletions arbnode/dataposter/dataposter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"testing"
"time"

"github.com/Knetic/govaluate"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -242,3 +243,25 @@ func (s *server) mux(w http.ResponseWriter, r *http.Request) {
fmt.Printf("error writing response: %v\n", err)
}
}

func TestMaxFeeCapFormulaCalculation(t *testing.T) {
// This test alerts, by failing, if the max fee cap formula were to be changed in the DefaultDataPosterConfig to
// use new variables other than the ones that are keys of 'parameters' map below
expression, err := govaluate.NewEvaluableExpression(DefaultDataPosterConfig.MaxFeeCapFormula)
if err != nil {
t.Fatalf("Error creating govaluate evaluable expression for calculating default maxFeeCap formula: %v", err)
}
config := DefaultDataPosterConfig
config.TargetPriceGwei = 0
p := &DataPoster{
config: func() *DataPosterConfig { return &config },
maxFeeCapExpression: expression,
}
result, err := p.evalMaxFeeCapExpr(0, 0)
if err != nil {
t.Fatalf("Error evaluating MaxFeeCap expression: %v", err)
}
if result.Cmp(common.Big0) != 0 {
t.Fatalf("Unexpected result. Got: %d, want: 0", result.Uint64())
}
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ replace github.com/VictoriaMetrics/fastcache => ./fastcache
replace github.com/ethereum/go-ethereum => ./go-ethereum

require (
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible
github.com/Shopify/toxiproxy v2.1.4+incompatible
github.com/alicebob/miniredis/v2 v2.21.0
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMd
github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8=
github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
Expand Down

0 comments on commit 01f66c2

Please sign in to comment.