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

Support upgrades in dynamicfees #1452

Draft
wants to merge 6 commits into
base: dynamic-fees-cleanup
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
6 changes: 3 additions & 3 deletions cmd/evm/internal/t8ntool/transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ type input struct {
}

func Transition(ctx *cli.Context) error {
var getTracer = func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) { return nil, nil }
getTracer := func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) { return nil, nil }

baseDir, err := createBasedir(ctx)
if err != nil {
Expand Down Expand Up @@ -230,7 +230,7 @@ func applyLondonChecks(env *stEnv, chainConfig *params.ChainConfig) error {
feeConfig.MinBaseFee = env.MinBaseFee
}
var err error
_, env.BaseFee, err = dummy.CalcBaseFee(chainConfig, feeConfig, parent, env.Timestamp)
env.BaseFee, err = dummy.CalcBaseFee(chainConfig, feeConfig, parent, env.Timestamp)
if err != nil {
return NewError(ErrorConfig, fmt.Errorf("failed calculating base fee: %v", err))
}
Expand Down Expand Up @@ -282,7 +282,7 @@ func saveFile(baseDir, filename string, data interface{}) error {
return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err))
}
location := path.Join(baseDir, filename)
if err = os.WriteFile(location, b, 0644); err != nil {
if err = os.WriteFile(location, b, 0o644); err != nil {
return NewError(ErrorIO, fmt.Errorf("failed writing output: %v", err))
}
log.Info("Wrote file", "file", location)
Expand Down
12 changes: 9 additions & 3 deletions consensus/dummy/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,14 +157,20 @@ func (eng *DummyEngine) verifyHeaderGasFields(config *params.ChainConfig, header
return nil
}

// Verify header.Extra and header.BaseFee match their expected values.
expectedExtraPrefix, expectedBaseFee, err := CalcBaseFee(config, feeConfig, parent, header.Time)
// Verify header.Extra matches the expected value.
expectedExtraPrefix, err := CalcExtraPrefix(config, feeConfig, parent, header.Time)
if err != nil {
return fmt.Errorf("failed to calculate base fee: %w", err)
return fmt.Errorf("failed to calculate extra prefix: %w", err)
}
if !bytes.HasPrefix(header.Extra, expectedExtraPrefix) {
return fmt.Errorf("expected header.Extra to have prefix: %x, found %x", expectedExtraPrefix, header.Extra)
}

// Verify header.BaseFee matches the expected value.
expectedBaseFee, err := CalcBaseFee(config, feeConfig, parent, header.Time)
if err != nil {
return fmt.Errorf("failed to calculate base fee: %w", err)
}
if !utils.BigEqual(header.BaseFee, expectedBaseFee) {
return fmt.Errorf("expected base fee (%d), found (%d)", expectedBaseFee, header.BaseFee)
}
Expand Down
143 changes: 107 additions & 36 deletions consensus/dummy/dynamic_fees.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,51 +21,69 @@ var (
errEstimateBaseFeeWithoutActivation = errors.New("cannot estimate base fee for chain without activation")
)

// CalcExtraPrefix takes the previous header and the timestamp of its child
// block and calculates the expected extra prefix for the child block.
func CalcExtraPrefix(
config *params.ChainConfig,
feeConfig commontype.FeeConfig,
parent *types.Header,
timestamp uint64,
) ([]byte, error) {
switch {
case config.IsSubnetEVM(timestamp):
window, err := calcFeeWindow(config, feeConfig, parent, timestamp)
if err != nil {
return nil, fmt.Errorf("failed to calculate fee window: %w", err)
}
return window.Bytes(), nil
default:
// Prior to SubnetEVM there was no expected extra prefix.
return nil, nil
}
}

// CalcBaseFee takes the previous header and the timestamp of its child block
// and calculates the expected base fee as well as the encoding of the past
// pricing information for the child block.
func CalcBaseFee(config *params.ChainConfig, feeConfig commontype.FeeConfig, parent *types.Header, timestamp uint64) ([]byte, *big.Int, error) {
// and calculates the expected base fee for the child block.
//
// Prior to SubnetEVM, the returned base fee will be nil.
func CalcBaseFee(config *params.ChainConfig, feeConfig commontype.FeeConfig, parent *types.Header, timestamp uint64) (*big.Int, error) {
switch {
case config.IsSubnetEVM(timestamp):
return calcBaseFeeWithWindow(config, feeConfig, parent, timestamp)
default:
// Prior to SubnetEVM the expected base fee is nil.
return nil, nil
}
}

// calcBaseFeeWithWindow should only be called if `timestamp` >= `config.SubnetEVMTimestamp`.
func calcBaseFeeWithWindow(config *params.ChainConfig, feeConfig commontype.FeeConfig, parent *types.Header, timestamp uint64) (*big.Int, error) {
// If the current block is the first EIP-1559 block, or it is the genesis block
// return the initial slice and initial base fee.
isSubnetEVM := config.IsSubnetEVM(parent.Time)
if !isSubnetEVM || parent.Number.Cmp(common.Big0) == 0 {
initialSlice := (&DynamicFeeWindow{}).Bytes()
return initialSlice, feeConfig.MinBaseFee, nil
if !config.IsSubnetEVM(parent.Time) || parent.Number.Cmp(common.Big0) == 0 {
return big.NewInt(feeConfig.MinBaseFee.Int64()), nil
}

dynamicFeeWindow, err := ParseDynamicFeeWindow(parent.Extra)
dynamicFeeWindow, err := calcFeeWindow(config, feeConfig, parent, timestamp)
if err != nil {
return nil, nil, err
return nil, err
}

if timestamp < parent.Time {
return nil, nil, fmt.Errorf("cannot calculate base fee for timestamp %d prior to parent timestamp %d", timestamp, parent.Time)
}
timeElapsed := timestamp - parent.Time

var (
baseFee = new(big.Int).Set(parent.BaseFee)
baseFeeChangeDenominator = feeConfig.BaseFeeChangeDenominator
parentGasTargetBig = feeConfig.TargetGas
parentGasTarget = parentGasTargetBig.Uint64()
parentGasTarget = feeConfig.TargetGas.Uint64()
totalGas = dynamicFeeWindow.Sum()
)

// Compute the new state of the gas rolling window.
dynamicFeeWindow.Add(parent.GasUsed)

// roll the window over by the timeElapsed to generate the new rollup
// window.
dynamicFeeWindow.Shift(timeElapsed)
dynamicFeeWindowBytes := dynamicFeeWindow.Bytes()

// Calculate the amount of gas consumed within the rollup window.
totalGas := dynamicFeeWindow.Sum()
if totalGas == parentGasTarget {
return dynamicFeeWindowBytes, baseFee, nil
return baseFee, nil
}

num := new(big.Int)

var (
num = new(big.Int)
parentGasTargetBig = new(big.Int).SetUint64(parentGasTarget)
)
if totalGas > parentGasTarget {
// If the parent block used more gas than its target, the baseFee should increase.
num.SetUint64(totalGas - parentGasTarget)
Expand All @@ -82,24 +100,78 @@ func CalcBaseFee(config *params.ChainConfig, feeConfig commontype.FeeConfig, par
num.Div(num, parentGasTargetBig)
num.Div(num, baseFeeChangeDenominator)
baseFeeDelta := math.BigMax(num, common.Big1)

if timestamp < parent.Time {
// This should never happen as the fee window calculations should
// have already failed, but it is kept for clarity.
return nil, fmt.Errorf("cannot calculate base fee for timestamp %d prior to parent timestamp %d",
timestamp,
parent.Time,
)
}

// If timeElapsed is greater than [params.RollupWindow], apply the
// state transition to the base fee to account for the interval during
// which no blocks were produced.
//
// We use timeElapsed/params.RollupWindow, so that the transition is
// applied for every [params.RollupWindow] seconds that has elapsed
// between the parent and this block.
if timeElapsed > params.RollupWindow {
// Note: timeElapsed/params.RollupWindow must be at least 1 since
// we've checked that timeElapsed > params.RollupWindow
baseFeeDelta = new(big.Int).Mul(baseFeeDelta, new(big.Int).SetUint64(timeElapsed/params.RollupWindow))
var (
timeElapsed = timestamp - parent.Time
windowsElapsed = timeElapsed / params.RollupWindow
)
if windowsElapsed > 1 {
bigWindowsElapsed := new(big.Int).SetUint64(windowsElapsed)
// Because baseFeeDelta could actually be [common.Big1], we must not
// modify the existing value of `baseFeeDelta` but instead allocate
// a new one.
baseFeeDelta = new(big.Int).Mul(baseFeeDelta, bigWindowsElapsed)
}
baseFee.Sub(baseFee, baseFeeDelta)
}

baseFee = selectBigWithinBounds(feeConfig.MinBaseFee, baseFee, MaxUint256)

return dynamicFeeWindowBytes, baseFee, nil
return baseFee, nil
}

// calcFeeWindow takes the previous header and the timestamp of its child block
// and calculates the expected fee window.
//
// calcFeeWindow should only be called if timestamp >= config.IsSubnetEVM
func calcFeeWindow(
config *params.ChainConfig,
feeConfig commontype.FeeConfig,
parent *types.Header,
timestamp uint64,
) (DynamicFeeWindow, error) {
// If the current block is the first EIP-1559 block, or it is the genesis block
// return the initial window.
if !config.IsSubnetEVM(parent.Time) || parent.Number.Cmp(common.Big0) == 0 {
return DynamicFeeWindow{}, nil
}

dynamicFeeWindow, err := ParseDynamicFeeWindow(parent.Extra)
if err != nil {
return DynamicFeeWindow{}, err
}

if timestamp < parent.Time {
return DynamicFeeWindow{}, fmt.Errorf("cannot calculate fee window for timestamp %d prior to parent timestamp %d",
timestamp,
parent.Time,
)
}
timeElapsed := timestamp - parent.Time

// Compute the new state of the gas rolling window.
dynamicFeeWindow.Add(parent.GasUsed)

// roll the window over by the timeElapsed to generate the new rollup
// window.
dynamicFeeWindow.Shift(timeElapsed)
return dynamicFeeWindow, nil
}

// EstimateNextBaseFee attempts to estimate the base fee of a block built at
Expand All @@ -116,8 +188,7 @@ func EstimateNextBaseFee(config *params.ChainConfig, feeConfig commontype.FeeCon
}

timestamp = max(timestamp, parent.Time, *config.SubnetEVMTimestamp)
_, baseFee, err := CalcBaseFee(config, feeConfig, parent, timestamp)
return baseFee, err
return CalcBaseFee(config, feeConfig, parent, timestamp)
}

// selectBigWithinBounds returns [value] if it is within the bounds:
Expand Down
8 changes: 6 additions & 2 deletions consensus/dummy/dynamic_fees_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,11 @@ func testDynamicFeesStaysWithinRange(t *testing.T, test test) {
BlockGasCostStep: big.NewInt(200_000),
}

nextExtraData, nextBaseFee, err := CalcBaseFee(params.TestChainConfig, testFeeConfig, header, block.timestamp)
nextExtraData, err := CalcExtraPrefix(params.TestChainConfig, testFeeConfig, header, block.timestamp)
if err != nil {
t.Fatalf("Failed to calculate extra prefix at index %d: %s", index, err)
}
nextBaseFee, err := CalcBaseFee(params.TestChainConfig, testFeeConfig, header, block.timestamp)
if err != nil {
t.Fatalf("Failed to calculate base fee at index %d: %s", index, err)
}
Expand Down Expand Up @@ -239,7 +243,7 @@ func TestCalcBaseFeeRegression(t *testing.T) {
MaxBlockGasCost: big.NewInt(1_000_000),
BlockGasCostStep: big.NewInt(200_000),
}
_, _, err := CalcBaseFee(params.TestChainConfig, testFeeConfig, parentHeader, timestamp)
_, err := CalcBaseFee(params.TestChainConfig, testFeeConfig, parentHeader, timestamp)
require.NoError(t, err)
require.Equalf(t, 0, common.Big1.Cmp(big.NewInt(1)), "big1 should be 1, got %s", common.Big1)
}
Expand Down
4 changes: 4 additions & 0 deletions core/blockchain_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,11 +361,15 @@ func (bc *BlockChain) SubscribeAcceptedTransactionEvent(ch chan<- NewTxsEvent) e
}

// GetFeeConfigAt returns the fee configuration and the last changed block number at [parent].
// If Subnet-EVM is not activated, returns default fee config and nil block number.
// If FeeManager is activated at [parent], returns the fee config in the precompile contract state.
// Otherwise returns the fee config in the chain config.
// Assumes that a valid configuration is stored when the precompile is activated.
func (bc *BlockChain) GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) {
config := bc.Config()
if !config.IsSubnetEVM(parent.Time) {
return params.DefaultFeeConfig, nil, nil
}
if !config.IsPrecompileEnabled(feemanager.ContractAddress, parent.Time) {
return config.FeeConfig, common.Big0, nil
}
Expand Down
24 changes: 14 additions & 10 deletions core/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,24 +379,28 @@ func (cm *chainMaker) makeHeader(parent *types.Block, gap uint64, state *state.S
ParentHash: parent.Hash(),
Coinbase: parent.Coinbase(),
Difficulty: engine.CalcDifficulty(cm, time, parent.Header()),
GasLimit: parent.GasLimit(),
Number: new(big.Int).Add(parent.Number(), common.Big1),
Time: time,
}
feeConfig, _, err := cm.GetFeeConfigAt(parent.Header())
if err != nil {
panic(err)
}
if cm.config.IsSubnetEVM(time) {
feeConfig, _, err := cm.GetFeeConfigAt(parent.Header())
if err != nil {
panic(err)
}

header.GasLimit = feeConfig.GasLimit.Uint64()
header.Extra, header.BaseFee, err = dummy.CalcBaseFee(cm.config, feeConfig, parent.Header(), time)
if err != nil {
panic(err)
}
} else {
header.GasLimit = CalcGasLimit(parent.GasUsed(), parent.GasLimit(), parent.GasLimit(), parent.GasLimit())
}

header.Extra, err = dummy.CalcExtraPrefix(cm.config, feeConfig, parent.Header(), time)
if err != nil {
panic(err)
}
header.BaseFee, err = dummy.CalcBaseFee(cm.config, feeConfig, parent.Header(), time)
if err != nil {
panic(err)
}

if cm.config.IsCancun(header.Number, header.Time) {
var (
parentExcessBlobGas uint64
Expand Down
14 changes: 7 additions & 7 deletions core/state_processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,11 @@ func TestStateProcessorErrors(t *testing.T) {
signer = types.LatestSigner(config)
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
)
var makeTx = func(key *ecdsa.PrivateKey, nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *types.Transaction {
makeTx := func(key *ecdsa.PrivateKey, nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *types.Transaction {
tx, _ := types.SignTx(types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data), signer, key)
return tx
}
var mkDynamicTx = func(nonce uint64, to common.Address, gasLimit uint64, gasTipCap, gasFeeCap *big.Int) *types.Transaction {
mkDynamicTx := func(nonce uint64, to common.Address, gasLimit uint64, gasTipCap, gasFeeCap *big.Int) *types.Transaction {
tx, _ := types.SignTx(types.NewTx(&types.DynamicFeeTx{
Nonce: nonce,
GasTipCap: gasTipCap,
Expand All @@ -80,7 +80,7 @@ func TestStateProcessorErrors(t *testing.T) {
}), signer, key1)
return tx
}
var mkDynamicCreationTx = func(nonce uint64, gasLimit uint64, gasTipCap, gasFeeCap *big.Int, data []byte) *types.Transaction {
mkDynamicCreationTx := func(nonce uint64, gasLimit uint64, gasTipCap, gasFeeCap *big.Int, data []byte) *types.Transaction {
tx, _ := types.SignTx(types.NewTx(&types.DynamicFeeTx{
Nonce: nonce,
GasTipCap: gasTipCap,
Expand All @@ -91,7 +91,7 @@ func TestStateProcessorErrors(t *testing.T) {
}), signer, key1)
return tx
}
var mkBlobTx = func(nonce uint64, to common.Address, gasLimit uint64, gasTipCap, gasFeeCap, blobGasFeeCap *big.Int, hashes []common.Hash) *types.Transaction {
mkBlobTx := func(nonce uint64, to common.Address, gasLimit uint64, gasTipCap, gasFeeCap, blobGasFeeCap *big.Int, hashes []common.Hash) *types.Transaction {
tx, err := types.SignTx(types.NewTx(&types.BlobTx{
Nonce: nonce,
GasTipCap: uint256.MustFromBig(gasTipCap),
Expand Down Expand Up @@ -444,9 +444,9 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
Time: parent.Time() + 10,
UncleHash: types.EmptyUncleHash,
}

header.Extra, _ = dummy.CalcExtraPrefix(config, config.FeeConfig, parent.Header(), header.Time)
header.BaseFee, _ = dummy.CalcBaseFee(config, config.FeeConfig, parent.Header(), header.Time)
if config.IsSubnetEVM(header.Time) {
header.Extra, header.BaseFee, _ = dummy.CalcBaseFee(config, config.FeeConfig, parent.Header(), header.Time)
header.BlockGasCost = big.NewInt(0)
}
var receipts []*types.Receipt
Expand All @@ -468,7 +468,7 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
}
header.Root = common.BytesToHash(hasher.Sum(nil))
if config.IsCancun(header.Number, header.Time) {
var pExcess, pUsed = uint64(0), uint64(0)
pExcess, pUsed := uint64(0), uint64(0)
if parent.ExcessBlobGas() != nil {
pExcess = *parent.ExcessBlobGas()
pUsed = *parent.BlobGasUsed()
Expand Down
Loading
Loading