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

feat: Constrain the Max to Target block size ratio #43

Merged
merged 4 commits into from
Dec 5, 2023
Merged
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
13 changes: 9 additions & 4 deletions x/feemarket/types/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ import (
"cosmossdk.io/math"
)

// MaxBlockUtilizationRatio is the maximum ratio of the max block size to the target block size. This
// can be trivially understood to be the maximum base fee increase that can occur in between
// blocks. This is a constant that is used to prevent the base fee from increasing too quickly.
const MaxBlockUtilizationRatio = 10

// NewParams instantiates a new EIP-1559 Params object. This params object is utilized
// to implement both the base EIP-1559 fee and AIMD EIP-1559 fee market implementations.
func NewParams(
Expand Down Expand Up @@ -64,14 +69,14 @@ func (p *Params) ValidateBasic() error {
return fmt.Errorf("target block size cannot be zero")
}

if p.MaxBlockUtilization == 0 {
return fmt.Errorf("max block size cannot be zero")
}

if p.TargetBlockUtilization > p.MaxBlockUtilization {
return fmt.Errorf("target block size cannot be greater than max block size")
}

if p.MaxBlockUtilization/p.TargetBlockUtilization > MaxBlockUtilizationRatio {
return fmt.Errorf("max block size cannot be greater than target block size times %d", MaxBlockUtilizationRatio)
}

if p.MinBaseFee.IsNil() || !p.MinBaseFee.GTE(math.ZeroInt()) {
return fmt.Errorf("min base fee cannot be nil and must be greater than or equal to zero")
}
Expand Down
14 changes: 14 additions & 0 deletions x/feemarket/types/params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,20 @@ func TestParams(t *testing.T) {
},
expectedErr: true,
},
{
name: "max to target block size ratio is too large",
p: types.Params{
Window: 1,
Alpha: math.LegacyMustNewDecFromStr("0.1"),
Beta: math.LegacyMustNewDecFromStr("0.1"),
Theta: math.LegacyMustNewDecFromStr("0.1"),
Delta: math.LegacyMustNewDecFromStr("0.1"),
TargetBlockUtilization: 2,
MaxBlockUtilization: 200,
FeeDenom: types.DefaultFeeDenom,
},
expectedErr: true,
},
{
name: "min base fee is nil",
p: types.Params{
Expand Down
37 changes: 26 additions & 11 deletions x/feemarket/types/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,15 @@ func (s *State) IncrementHeight() {
// based on the average utilization of the block window. The base fee is
// update using the new learning rate and the delta adjustment. Please
// see the EIP-1559 specification for more details.
func (s *State) UpdateBaseFee(params Params) math.Int {
func (s *State) UpdateBaseFee(params Params) (fee math.Int) {
// Panic catch in case there is an overflow
defer func() {
if rec := recover(); rec != nil {
s.BaseFee = params.MinBaseFee
fee = s.BaseFee
}
}()
aljo242 marked this conversation as resolved.
Show resolved Hide resolved

// Calculate the new base fee with the learning rate adjustment.
currentBlockSize := math.LegacyNewDecFromInt(math.NewIntFromUint64(s.Window[s.Index]))
targetBlockSize := math.LegacyNewDecFromInt(math.NewIntFromUint64(params.TargetBlockUtilization))
Expand All @@ -62,7 +70,7 @@ func (s *State) UpdateBaseFee(params Params) math.Int {
net := math.LegacyNewDecFromInt(s.GetNetUtilization(params)).Mul(params.Delta)

// Update the base fee.
fee := (math.LegacyNewDecFromInt(s.BaseFee).Mul(learningRateAdjustment)).Add(net).TruncateInt()
fee = (math.LegacyNewDecFromInt(s.BaseFee).Mul(learningRateAdjustment)).Add(net).TruncateInt()

// Ensure the base fee is greater than the minimum base fee.
if fee.LT(params.MinBaseFee) {
Expand All @@ -86,27 +94,34 @@ func (s *State) UpdateBaseFee(params Params) math.Int {
// when blocks are relatively close to the target block utilization.
//
// For more details, please see the EIP-1559 specification.
func (s *State) UpdateLearningRate(params Params) math.LegacyDec {
func (s *State) UpdateLearningRate(params Params) (lr math.LegacyDec) {
// Panic catch in case there is an overflow
defer func() {
if rec := recover(); rec != nil {
s.LearningRate = params.MinLearningRate
lr = s.LearningRate
}
}()

// Calculate the average utilization of the block window.
avg := s.GetAverageUtilization(params)

// Determine if the average utilization is above or below the target
// threshold and adjust the learning rate accordingly.
var updatedLearningRate math.LegacyDec
if avg.LTE(params.Theta) || avg.GTE(math.LegacyOneDec().Sub(params.Theta)) {
updatedLearningRate = params.Alpha.Add(s.LearningRate)
if updatedLearningRate.GT(params.MaxLearningRate) {
updatedLearningRate = params.MaxLearningRate
lr = params.Alpha.Add(s.LearningRate)
if lr.GT(params.MaxLearningRate) {
lr = params.MaxLearningRate
}
} else {
updatedLearningRate = s.LearningRate.Mul(params.Beta)
if updatedLearningRate.LT(params.MinLearningRate) {
updatedLearningRate = params.MinLearningRate
lr = s.LearningRate.Mul(params.Beta)
if lr.LT(params.MinLearningRate) {
lr = params.MinLearningRate
}
}

// Update the current learning rate.
s.LearningRate = updatedLearningRate
s.LearningRate = lr
return s.LearningRate
}

Expand Down
29 changes: 29 additions & 0 deletions x/feemarket/types/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,35 @@ func TestState_UpdateBaseFee(t *testing.T) {
require.Equal(t, expectedLR, lr)
require.Equal(t, expectedFee, bf)
})

t.Run("recovers from overflow with large max block utilization ratio", func(t *testing.T) {
state := types.DefaultAIMDState()
state.Window = make([]uint64, 50)
state.BaseFee = state.BaseFee.Mul(math.NewInt(10))

params := types.DefaultAIMDParams()
params.Window = 50
// This should overflow the base fee after a few iterations.
params.TargetBlockUtilization = 1
params.MaxBlockUtilization = 9_999_999_999_999_999_999

for {
var baseFee math.Int
require.NotPanics(t, func() {
state.Update(params.MaxBlockUtilization, params)
state.UpdateLearningRate(params)
baseFee = state.UpdateBaseFee(params)
})

// An overflow should have occurred.
if baseFee.Equal(params.MinBaseFee) {
return
}

// Update the height and try again.
state.IncrementHeight()
}
})
}

func TestState_UpdateLearningRate(t *testing.T) {
Expand Down
Loading