From 49ad954a77c987b6531b64b5be612d5b1b346300 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 29 Jul 2024 08:15:53 -0700 Subject: [PATCH] eupgrade: lowering the base fee (#604) * eupgrade: base definitions * updates * nit: consistently use EUpgrade for replace later * update test configs * refactor json genesis specs in vm_test.go * set min base fee at EUpgrade * nit: comment * Update params/config.go Signed-off-by: Darioush Jalali * add ut * nit: comment --------- Signed-off-by: Darioush Jalali --- consensus/dummy/dynamic_fees.go | 4 +++ consensus/dummy/dynamic_fees_test.go | 27 +++++++++++++++ core/txpool/blobpool/blobpool_test.go | 6 ++-- core/txpool/txpool.go | 8 +++++ params/avalanche_params.go | 1 + params/config.go | 2 +- plugin/evm/vm.go | 31 ++++++++++++++++- plugin/evm/vm_test.go | 48 ++++++++++++++++++++++++++- 8 files changed, 121 insertions(+), 6 deletions(-) diff --git a/consensus/dummy/dynamic_fees.go b/consensus/dummy/dynamic_fees.go index 1f7d375d..b7edc851 100644 --- a/consensus/dummy/dynamic_fees.go +++ b/consensus/dummy/dynamic_fees.go @@ -21,6 +21,7 @@ var ( ApricotPhase4MinBaseFee = big.NewInt(params.ApricotPhase4MinBaseFee) ApricotPhase4MaxBaseFee = big.NewInt(params.ApricotPhase4MaxBaseFee) ApricotPhase3InitialBaseFee = big.NewInt(params.ApricotPhase3InitialBaseFee) + EUpgradeMinBaseFee = big.NewInt(params.EUpgradeMinBaseFee) ApricotPhase4BaseFeeChangeDenominator = new(big.Int).SetUint64(params.ApricotPhase4BaseFeeChangeDenominator) ApricotPhase5BaseFeeChangeDenominator = new(big.Int).SetUint64(params.ApricotPhase5BaseFeeChangeDenominator) @@ -45,6 +46,7 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header, timestamp uin isApricotPhase3 = config.IsApricotPhase3(parent.Time) isApricotPhase4 = config.IsApricotPhase4(parent.Time) isApricotPhase5 = config.IsApricotPhase5(parent.Time) + isEUpgrade = config.IsEUpgrade(parent.Time) ) if !isApricotPhase3 || parent.Number.Cmp(common.Big0) == 0 { initialSlice := make([]byte, params.DynamicFeeExtraDataSize) @@ -177,6 +179,8 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header, timestamp uin // Ensure that the base fee does not increase/decrease outside of the bounds switch { + case isEUpgrade: + baseFee = selectBigWithinBounds(EUpgradeMinBaseFee, baseFee, nil) case isApricotPhase5: baseFee = selectBigWithinBounds(ApricotPhase4MinBaseFee, baseFee, nil) case isApricotPhase4: diff --git a/consensus/dummy/dynamic_fees_test.go b/consensus/dummy/dynamic_fees_test.go index c8218140..2fb90c84 100644 --- a/consensus/dummy/dynamic_fees_test.go +++ b/consensus/dummy/dynamic_fees_test.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func testRollup(t *testing.T, longs []uint64, roll int) { @@ -537,3 +538,29 @@ func TestCalcBlockGasCost(t *testing.T) { }) } } + +func TestDynamicFeesEUpgrade(t *testing.T) { + require := require.New(t) + header := &types.Header{ + Number: big.NewInt(0), + } + + timestamp := uint64(1) + extra, nextBaseFee, err := CalcBaseFee(params.TestEUpgradeChainConfig, header, timestamp) + require.NoError(err) + // Genesis matches the initial base fee + require.Equal(params.ApricotPhase3InitialBaseFee, nextBaseFee.Int64()) + + timestamp = uint64(10_000) + header = &types.Header{ + Number: big.NewInt(1), + Time: header.Time, + BaseFee: nextBaseFee, + Extra: extra, + } + _, nextBaseFee, err = CalcBaseFee(params.TestEUpgradeChainConfig, header, timestamp) + require.NoError(err) + // After some time has passed in the EUpgrade phase, the base fee should drop + // lower than the prior base fee minimum. + require.Less(nextBaseFee.Int64(), params.ApricotPhase4MinBaseFee) +} diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index ecd15f82..aaff113f 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -91,9 +91,9 @@ func init() { // overrideMinFee sets the minimum base fee to 1 wei for the duration of the test. func overrideMinFee(t *testing.T) { - orig := dummy.ApricotPhase4MinBaseFee - dummy.ApricotPhase4MinBaseFee = big.NewInt(1) - t.Cleanup(func() { dummy.ApricotPhase4MinBaseFee = orig }) + orig := dummy.EUpgradeMinBaseFee + dummy.EUpgradeMinBaseFee = big.NewInt(1) + t.Cleanup(func() { dummy.EUpgradeMinBaseFee = orig }) } // testBlockChain is a mock of the live chain for testing the pool. diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go index 847576a0..39da2132 100644 --- a/core/txpool/txpool.go +++ b/core/txpool/txpool.go @@ -90,6 +90,7 @@ type TxPool struct { quit chan chan error // Quit channel to tear down the head updater gasTip atomic.Pointer[big.Int] // Remember last value set so it can be retrieved + minFee atomic.Pointer[big.Int] // Remember last value set so it can be retrieved (in tests) reorgFeed event.Feed } @@ -270,9 +271,16 @@ func (p *TxPool) SetGasTip(tip *big.Int) { } } +// MinFee returns the current minimum fee enforced by the transaction pool. +func (p *TxPool) MinFee() *big.Int { + return new(big.Int).Set(p.minFee.Load()) +} + // SetMinFee updates the minimum fee required by the transaction pool for a // new transaction, and drops all transactions below this threshold. func (p *TxPool) SetMinFee(fee *big.Int) { + p.minFee.Store(new(big.Int).Set(fee)) + for _, subpool := range p.subpools { subpool.SetMinFee(fee) } diff --git a/params/avalanche_params.go b/params/avalanche_params.go index 58cbcc65..c222034f 100644 --- a/params/avalanche_params.go +++ b/params/avalanche_params.go @@ -30,6 +30,7 @@ const ( ApricotPhase4BaseFeeChangeDenominator uint64 = 12 ApricotPhase5TargetGas uint64 = 15_000_000 ApricotPhase5BaseFeeChangeDenominator uint64 = 36 + EUpgradeMinBaseFee int64 = GWei DynamicFeeExtraDataSize = 80 RollupWindow uint64 = 10 diff --git a/params/config.go b/params/config.go index abc4a27a..331446d2 100644 --- a/params/config.go +++ b/params/config.go @@ -540,7 +540,7 @@ type ChainConfig struct { // Note: EIP-4895 is excluded since withdrawals are not relevant to the Avalanche C-Chain or Subnets running the EVM. DurangoBlockTimestamp *uint64 `json:"durangoBlockTimestamp,omitempty"` // EUpgrade on the Avalanche network. (nil = no fork, 0 = already activated) - // It activates Cancun (TODO) and modifies the min base fee (TODO). + // It activates Cancun and reduces the min base fee. EUpgradeTime *uint64 `json:"eUpgradeTime,omitempty"` // Cancun activates the Cancun upgrade from Ethereum. (nil = no fork, 0 = already activated) diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 29172294..eef02d4f 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -703,12 +703,41 @@ func (vm *VM) initializeChain(lastAcceptedHash common.Hash) error { // Set the gas parameters for the tx pool to the minimum gas price for the // latest upgrade. vm.txPool.SetGasTip(big.NewInt(0)) - vm.txPool.SetMinFee(big.NewInt(params.ApricotPhase4MinBaseFee)) + vm.setMinFeeAtEUpgrade() vm.eth.Start() return vm.initChainState(vm.blockChain.LastAcceptedBlock()) } +// TODO: remove this after EUpgrade is activated +func (vm *VM) setMinFeeAtEUpgrade() { + now := vm.clock.Time() + if vm.chainConfig.EUpgradeTime == nil { + // If EUpgrade is not set, set the min fee according to the latest upgrade + vm.txPool.SetMinFee(big.NewInt(params.ApricotPhase4MinBaseFee)) + return + } else if vm.chainConfig.IsEUpgrade(uint64(now.Unix())) { + // If EUpgrade is activated, set the min fee to the EUpgrade min fee + vm.txPool.SetMinFee(big.NewInt(params.EUpgradeMinBaseFee)) + return + } + + vm.txPool.SetMinFee(big.NewInt(params.ApricotPhase4MinBaseFee)) + vm.shutdownWg.Add(1) + go func() { + defer vm.shutdownWg.Done() + + wait := utils.Uint64ToTime(vm.chainConfig.EUpgradeTime).Sub(now) + t := time.NewTimer(wait) + select { + case <-t.C: // Wait for EUpgrade to be activated + vm.txPool.SetMinFee(big.NewInt(params.EUpgradeMinBaseFee)) + case <-vm.shutdownChan: + } + t.Stop() + }() +} + // initializeStateSyncClient initializes the client for performing state sync. // If state sync is disabled, this function will wipe any ongoing summary from // disk to ensure that we do not continue syncing from an invalid snapshot. diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index bcadcb72..e7e70287 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -48,6 +48,7 @@ import ( "github.com/ava-labs/avalanchego/utils/hashing" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/components/chain" @@ -113,6 +114,12 @@ var ( return &cpy } + activateEUpgrade = func(cfg *params.ChainConfig, eUpgradeTime uint64) *params.ChainConfig { + cpy := *cfg + cpy.EUpgradeTime = &eUpgradeTime + return &cpy + } + genesisJSONApricotPhase0 = genesisJSON(params.TestLaunchConfig) genesisJSONApricotPhase1 = genesisJSON(params.TestApricotPhase1Config) genesisJSONApricotPhase2 = genesisJSON(params.TestApricotPhase2Config) @@ -281,7 +288,24 @@ func GenesisVM(t *testing.T, *atomic.Memory, *commonEng.SenderTest, ) { - vm := &VM{} + return GenesisVMWithClock(t, finishBootstrapping, genesisJSON, configJSON, upgradeJSON, mockable.Clock{}) +} + +// GenesisVMWithClock creates a VM instance as GenesisVM does, but also allows +// setting the vm's time before [Initialize] is called. +func GenesisVMWithClock( + t *testing.T, + finishBootstrapping bool, + genesisJSON string, + configJSON string, + upgradeJSON string, + clock mockable.Clock, +) (chan commonEng.Message, + *VM, database.Database, + *atomic.Memory, + *commonEng.SenderTest, +) { + vm := &VM{clock: clock} vm.p2pSender = &commonEng.FakeSender{} ctx, dbManager, genesisBytes, issuer, m := setupGenesis(t, genesisJSON) appSender := &commonEng.SenderTest{T: t} @@ -4065,3 +4089,25 @@ func TestParentBeaconRootBlock(t *testing.T) { }) } } + +func TestMinFeeSetAtEUpgrade(t *testing.T) { + require := require.New(t) + now := time.Now() + eUpgradeTime := uint64(now.Add(1 * time.Second).Unix()) + + genesis := genesisJSON( + activateEUpgrade(params.TestEUpgradeChainConfig, eUpgradeTime), + ) + clock := mockable.Clock{} + clock.Set(now) + + _, vm, _, _, _ := GenesisVMWithClock(t, false, genesis, "", "", clock) + initial := vm.txPool.MinFee() + require.Equal(params.ApricotPhase4MinBaseFee, initial.Int64()) + + require.Eventually( + func() bool { return params.EUpgradeMinBaseFee == vm.txPool.MinFee().Int64() }, + 5*time.Second, + 1*time.Second, + ) +}