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

Refactor blockgascost #1448

Open
wants to merge 4 commits into
base: merge-coreth-master
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions commontype/fee_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ func (f *FeeConfig) Verify() error {
return fmt.Errorf("minBlockGasCost = %d cannot be greater than maxBlockGasCost = %d", f.MinBlockGasCost, f.MaxBlockGasCost)
case f.BlockGasCostStep.Cmp(common.Big0) == -1:
return fmt.Errorf("blockGasCostStep = %d cannot be less than 0", f.BlockGasCostStep)
case !f.MaxBlockGasCost.IsUint64():
return fmt.Errorf("maxBlockGasCost = %d is not a valid uint64", f.MaxBlockGasCost)
}
return f.checkByteLens()
}
Expand Down
74 changes: 32 additions & 42 deletions consensus/dummy/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/trie"
"github.com/ava-labs/subnet-evm/utils"
"github.com/ava-labs/subnet-evm/vmerrs"
"github.com/ethereum/go-ethereum/common"

Expand All @@ -26,11 +27,10 @@ import (
var (
allowedFutureBlockTime = 10 * time.Second // Max time from current time allowed for blocks, before they're considered future blocks

errInvalidBlockTime = errors.New("timestamp less than parent's")
errUnclesUnsupported = errors.New("uncles unsupported")
errBlockGasCostNil = errors.New("block gas cost is nil")
errBlockGasCostTooLarge = errors.New("block gas cost is not uint64")
errBaseFeeNil = errors.New("base fee is nil")
errInvalidBlockTime = errors.New("timestamp less than parent's")
errUnclesUnsupported = errors.New("uncles unsupported")
errBlockGasCostNil = errors.New("block gas cost is nil")
errBaseFeeNil = errors.New("base fee is nil")
)

type Mode struct {
Expand Down Expand Up @@ -180,21 +180,12 @@ func (eng *DummyEngine) verifyHeaderGasFields(config *params.ChainConfig, header
}

// Enforce BlockGasCost constraints
expectedBlockGasCost := calcBlockGasCost(
feeConfig.TargetBlockRate,
feeConfig.MinBlockGasCost,
feeConfig.MaxBlockGasCost,
feeConfig.BlockGasCostStep,
parent.BlockGasCost,
parent.Time, header.Time,
expectedBlockGasCost := customheader.BlockGasCost(
feeConfig,
parent,
header.Time,
)
if header.BlockGasCost == nil {
return errBlockGasCostNil
}
if !header.BlockGasCost.IsUint64() {
return errBlockGasCostTooLarge
}
if header.BlockGasCost.Cmp(expectedBlockGasCost) != 0 {
if !utils.BigEqualUint64(header.BlockGasCost, expectedBlockGasCost) {
return fmt.Errorf("invalid block gas cost: have %d, want %d", header.BlockGasCost, expectedBlockGasCost)
}
return nil
Expand Down Expand Up @@ -358,32 +349,31 @@ func (eng *DummyEngine) verifyBlockFee(
}

func (eng *DummyEngine) Finalize(chain consensus.ChainHeaderReader, block *types.Block, parent *types.Header, state *state.StateDB, receipts []*types.Receipt) error {
if chain.Config().IsSubnetEVM(block.Time()) {
config := chain.Config()
timestamp := block.Time()
if config.IsSubnetEVM(block.Time()) {
// we use the parent to determine the fee config
// since the current block has not been finalized yet.
feeConfig, _, err := chain.GetFeeConfigAt(parent)
if err != nil {
return err
}

// Calculate the expected blockGasCost for this block.
// Note: this is a deterministic transtion that defines an exact block fee for this block.
blockGasCost := calcBlockGasCost(
feeConfig.TargetBlockRate,
feeConfig.MinBlockGasCost,
feeConfig.MaxBlockGasCost,
feeConfig.BlockGasCostStep,
parent.BlockGasCost,
parent.Time, block.Time(),
// Verify the BlockGasCost set in the header matches the expected value.
blockGasCost := block.BlockGasCost()
expectedBlockGasCost := customheader.BlockGasCost(
feeConfig,
parent,
timestamp,
)
// Verify the BlockGasCost set in the header matches the calculated value.
if blockBlockGasCost := block.BlockGasCost(); blockBlockGasCost == nil || !blockBlockGasCost.IsUint64() || blockBlockGasCost.Cmp(blockGasCost) != 0 {
return fmt.Errorf("invalid blockGasCost: have %d, want %d", blockBlockGasCost, blockGasCost)
if !utils.BigEqualUint64(blockGasCost, expectedBlockGasCost) {
return fmt.Errorf("invalid blockGasCost: have %d, want %d", blockGasCost, expectedBlockGasCost)
}

// Verify the block fee was paid.
if err := eng.verifyBlockFee(
block.BaseFee(),
block.BlockGasCost(),
blockGasCost,
block.Transactions(),
receipts,
); err != nil {
Expand All @@ -397,22 +387,22 @@ func (eng *DummyEngine) Finalize(chain consensus.ChainHeaderReader, block *types
func (eng *DummyEngine) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, parent *types.Header, state *state.StateDB, txs []*types.Transaction,
uncles []*types.Header, receipts []*types.Receipt,
) (*types.Block, error) {
if chain.Config().IsSubnetEVM(header.Time) {
config := chain.Config()
if config.IsSubnetEVM(header.Time) {
// we use the parent to determine the fee config
// since the current block has not been finalized yet.
feeConfig, _, err := chain.GetFeeConfigAt(parent)
if err != nil {
return nil, err
}
// Calculate the required block gas cost for this block.
header.BlockGasCost = calcBlockGasCost(
feeConfig.TargetBlockRate,
feeConfig.MinBlockGasCost,
feeConfig.MaxBlockGasCost,
feeConfig.BlockGasCostStep,
parent.BlockGasCost,
parent.Time, header.Time,
blockGasCost := customheader.BlockGasCost(
feeConfig,
parent,
header.Time,
)
header.BlockGasCost = new(big.Int).SetUint64(blockGasCost)

// Verify that this block covers the block fee.
if err := eng.verifyBlockFee(
header.BaseFee,
Expand All @@ -424,7 +414,7 @@ func (eng *DummyEngine) FinalizeAndAssemble(chain consensus.ChainHeaderReader, h
}
}
// commit the final state root
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
header.Root = state.IntermediateRoot(config.IsEIP158(header.Number))

// Header seems complete, assemble into a block and return
return types.NewBlock(
Expand Down
63 changes: 27 additions & 36 deletions consensus/dummy/consensus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,32 @@ import (
"math/big"
"testing"

"github.com/ava-labs/subnet-evm/commontype"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/plugin/evm/header"
"github.com/ethereum/go-ethereum/common"
)

var testBlockGasCostStep = big.NewInt(50_000)
var testFeeConfig = commontype.FeeConfig{
MinBlockGasCost: big.NewInt(0),
MaxBlockGasCost: big.NewInt(1_000_000),
TargetBlockRate: 2,
BlockGasCostStep: big.NewInt(50_000),
}

func TestVerifyBlockFee(t *testing.T) {
tests := map[string]struct {
baseFee *big.Int
parentBlockGasCost *big.Int
parentTime, currentTime uint64
txs []*types.Transaction
receipts []*types.Receipt
shouldErr bool
baseFee *big.Int
parentBlockGasCost *big.Int
timeElapsed uint64
txs []*types.Transaction
receipts []*types.Receipt
shouldErr bool
}{
"tx only base fee": {
baseFee: big.NewInt(100),
parentBlockGasCost: big.NewInt(0),
parentTime: 10,
currentTime: 10,
timeElapsed: 0,
txs: []*types.Transaction{
types.NewTransaction(0, common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), big.NewInt(0), 100, big.NewInt(100), nil),
},
Expand All @@ -40,8 +45,7 @@ func TestVerifyBlockFee(t *testing.T) {
"tx covers exactly block fee": {
baseFee: big.NewInt(100),
parentBlockGasCost: big.NewInt(0),
parentTime: 10,
currentTime: 10,
timeElapsed: 0,
txs: []*types.Transaction{
types.NewTransaction(0, common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), big.NewInt(0), 100_000, big.NewInt(200), nil),
},
Expand All @@ -53,8 +57,7 @@ func TestVerifyBlockFee(t *testing.T) {
"txs share block fee": {
baseFee: big.NewInt(100),
parentBlockGasCost: big.NewInt(0),
parentTime: 10,
currentTime: 10,
timeElapsed: 0,
txs: []*types.Transaction{
types.NewTransaction(0, common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), big.NewInt(0), 100_000, big.NewInt(200), nil),
types.NewTransaction(1, common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), big.NewInt(0), 100_000, big.NewInt(100), nil),
Expand All @@ -68,8 +71,7 @@ func TestVerifyBlockFee(t *testing.T) {
"txs split block fee": {
baseFee: big.NewInt(100),
parentBlockGasCost: big.NewInt(0),
parentTime: 10,
currentTime: 10,
timeElapsed: 0,
txs: []*types.Transaction{
types.NewTransaction(0, common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), big.NewInt(0), 100_000, big.NewInt(150), nil),
types.NewTransaction(1, common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), big.NewInt(0), 100_000, big.NewInt(150), nil),
Expand All @@ -83,8 +85,7 @@ func TestVerifyBlockFee(t *testing.T) {
"tx only base fee after full time window": {
baseFee: big.NewInt(100),
parentBlockGasCost: big.NewInt(500_000),
parentTime: 10,
currentTime: 22, // 2s target + 10
timeElapsed: testFeeConfig.TargetBlockRate + 10,
txs: []*types.Transaction{
types.NewTransaction(0, common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), big.NewInt(0), 100, big.NewInt(100), nil),
},
Expand All @@ -96,8 +97,7 @@ func TestVerifyBlockFee(t *testing.T) {
"tx only base fee after large time window": {
baseFee: big.NewInt(100),
parentBlockGasCost: big.NewInt(100_000),
parentTime: 0,
currentTime: math.MaxUint64,
timeElapsed: math.MaxUint64,
txs: []*types.Transaction{
types.NewTransaction(0, common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), big.NewInt(0), 100, big.NewInt(100), nil),
},
Expand All @@ -106,29 +106,20 @@ func TestVerifyBlockFee(t *testing.T) {
},
shouldErr: false,
},
"parent time > current time": {
baseFee: big.NewInt(100),
parentBlockGasCost: big.NewInt(0),
parentTime: 11,
currentTime: 10,
txs: nil,
receipts: nil,
shouldErr: true,
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
blockGasCost := calcBlockGasCost(
params.DefaultFeeConfig.TargetBlockRate,
params.DefaultFeeConfig.MinBlockGasCost,
params.DefaultFeeConfig.MaxBlockGasCost,
testBlockGasCostStep,
blockGasCost := header.BlockGasCostWithStep(
testFeeConfig,
test.parentBlockGasCost,
test.parentTime, test.currentTime,
testFeeConfig.BlockGasCostStep.Uint64(),
test.timeElapsed,
)
bigBlockGasCost := new(big.Int).SetUint64(blockGasCost)

engine := NewFaker()
if err := engine.verifyBlockFee(test.baseFee, blockGasCost, test.txs, test.receipts); err != nil {
if err := engine.verifyBlockFee(test.baseFee, bigBlockGasCost, test.txs, test.receipts); err != nil {
if !test.shouldErr {
t.Fatalf("Unexpected error: %s", err)
}
Expand Down
72 changes: 19 additions & 53 deletions consensus/dummy/dynamic_fees.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ func CalcBaseFee(config *params.ChainConfig, feeConfig commontype.FeeConfig, par
return nil, 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

// start off with parent's base fee
baseFee := new(big.Int).Set(parent.BaseFee)
baseFeeChangeDenominator := feeConfig.BaseFeeChangeDenominator
Expand All @@ -41,14 +46,9 @@ func CalcBaseFee(config *params.ChainConfig, feeConfig commontype.FeeConfig, par
// Compute the new state of the gas rolling window.
dynamicFeeWindow.Add(parent.GasUsed)

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

// roll the window over by the difference between the timestamps to generate
// the new rollup window.
dynamicFeeWindow.Shift(roll)
// 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.
Expand All @@ -75,14 +75,17 @@ func CalcBaseFee(config *params.ChainConfig, feeConfig commontype.FeeConfig, par
num.Div(num, parentGasTargetBig)
num.Div(num, baseFeeChangeDenominator)
baseFeeDelta := math.BigMax(num, common.Big1)

// If [roll] is greater than [rollupWindow], apply the state transition to the base fee to account
// for the interval during which no blocks were produced.
// We use roll/rollupWindow, so that the transition is applied for every [rollupWindow] seconds
// that has elapsed between the parent and this block.
if roll > params.RollupWindow {
// Note: roll/params.RollupWindow must be greater than 1 since we've checked that roll > params.RollupWindow
baseFeeDelta = new(big.Int).Mul(baseFeeDelta, new(big.Int).SetUint64(roll/params.RollupWindow))
// 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))
}
baseFee.Sub(baseFee, baseFeeDelta)
}
Expand Down Expand Up @@ -119,43 +122,6 @@ func selectBigWithinBounds(lowerBound, value, upperBound *big.Int) *big.Int {
}
}

// calcBlockGasCost calculates the required block gas cost. If [parentTime]
// > [currentTime], the timeElapsed will be treated as 0.
func calcBlockGasCost(
targetBlockRate uint64,
minBlockGasCost *big.Int,
maxBlockGasCost *big.Int,
blockGasCostStep *big.Int,
parentBlockGasCost *big.Int,
parentTime, currentTime uint64,
) *big.Int {
// Handle Subnet EVM boundary by returning the minimum value as the boundary.
if parentBlockGasCost == nil {
return new(big.Int).Set(minBlockGasCost)
}

// Treat an invalid parent/current time combination as 0 elapsed time.
var timeElapsed uint64
if parentTime <= currentTime {
timeElapsed = currentTime - parentTime
}

var blockGasCost *big.Int
if timeElapsed < targetBlockRate {
blockGasCostDelta := new(big.Int).Mul(blockGasCostStep, new(big.Int).SetUint64(targetBlockRate-timeElapsed))
blockGasCost = new(big.Int).Add(parentBlockGasCost, blockGasCostDelta)
} else {
blockGasCostDelta := new(big.Int).Mul(blockGasCostStep, new(big.Int).SetUint64(timeElapsed-targetBlockRate))
blockGasCost = new(big.Int).Sub(parentBlockGasCost, blockGasCostDelta)
}

blockGasCost = selectBigWithinBounds(minBlockGasCost, blockGasCost, maxBlockGasCost)
if !blockGasCost.IsUint64() {
blockGasCost = new(big.Int).SetUint64(math.MaxUint64)
}
return blockGasCost
}

// MinRequiredTip is the estimated minimum tip a transaction would have
// needed to pay to be included in a given block (assuming it paid a tip
// proportional to its gas usage). In reality, there is no minimum tip that
Expand Down
Loading
Loading