Skip to content
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
10 changes: 10 additions & 0 deletions consensus/beacon/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,16 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
}
}

// Store DA footprint in BlobGasUsed header field if it hasn't already been set yet.
// Builder code may already calculate it during block building to avoid recalculating it here.
if chain.Config().IsDAFootprintBlockLimit(header.Time) && (header.BlobGasUsed == nil || *header.BlobGasUsed == 0) {
daFootprint, err := types.CalcDAFootprint(body.Transactions)
if err != nil {
return nil, fmt.Errorf("error calculating DA footprint: %w", err)
}
header.BlobGasUsed = &daFootprint
}
Comment thread
sebastianst marked this conversation as resolved.

Comment thread
sebastianst marked this conversation as resolved.
// Assemble the final block.
block := types.NewBlock(header, body, receipts, trie.NewStackTrie(nil), chain.Config())

Expand Down
26 changes: 18 additions & 8 deletions consensus/misc/eip1559/eip1559.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func VerifyEIP1559Header(config *params.ChainConfig, parent, header *types.Heade
if !config.IsLondon(parent.Number) {
parentGasLimit = parent.GasLimit * config.ElasticityMultiplier()
}
if config.Optimism == nil { // gasLimit can adjust instantly in optimism
if !config.IsOptimism() { // OP Stack gasLimit can adjust instantly
if err := misc.VerifyGaslimit(parentGasLimit, header.GasLimit); err != nil {
return err
}
Expand Down Expand Up @@ -75,7 +75,7 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header, time uint64)
}

// OPStack addition: calculate the base fee using the upstream code.
baseFee := calcBaseFeeInner(parent, elasticity, denominator)
baseFee := calcBaseFeeInner(config, parent, elasticity, denominator)

// OPStack addition: enforce minimum base fee.
// If the minimum base fee is 0, this has no effect.
Expand All @@ -89,10 +89,20 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header, time uint64)
return baseFee
}

func calcBaseFeeInner(parent *types.Header, elasticity uint64, denominator uint64) *big.Int {
func calcBaseFeeInner(config *params.ChainConfig, parent *types.Header, elasticity uint64, denominator uint64) *big.Int {
parentGasTarget := parent.GasLimit / elasticity
// If the parent gasUsed is the same as the target, the baseFee remains unchanged.
if parent.GasUsed == parentGasTarget {
parentGasMetered := parent.GasUsed
if config.IsDAFootprintBlockLimit(parent.Time) {
if parent.BlobGasUsed == nil {
panic("Jovian parent block has nil BlobGasUsed")
} else if *parent.BlobGasUsed > parent.GasUsed {
Comment thread
sebastianst marked this conversation as resolved.
// Jovian updates the base fee based on the maximum of total transactions gas used and total DA footprint (which is
// stored in the BlobGasUsed field of the header).
parentGasMetered = *parent.BlobGasUsed
}
}
// If the parent gasMetered is the same as the target, the baseFee remains unchanged.
if parentGasMetered == parentGasTarget {
return new(big.Int).Set(parent.BaseFee)
}

Expand All @@ -101,10 +111,10 @@ func calcBaseFeeInner(parent *types.Header, elasticity uint64, denominator uint6
denom = new(big.Int)
)

if parent.GasUsed > parentGasTarget {
if parentGasMetered > parentGasTarget {
// If the parent block used more gas than its target, the baseFee should increase.
// max(1, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)
num.SetUint64(parent.GasUsed - parentGasTarget)
num.SetUint64(parentGasMetered - parentGasTarget)
num.Mul(num, parent.BaseFee)
num.Div(num, denom.SetUint64(parentGasTarget))
num.Div(num, denom.SetUint64(denominator))
Expand All @@ -115,7 +125,7 @@ func calcBaseFeeInner(parent *types.Header, elasticity uint64, denominator uint6
} else {
// Otherwise if the parent block used less gas than its target, the baseFee should decrease.
// max(0, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)
num.SetUint64(parentGasTarget - parent.GasUsed)
num.SetUint64(parentGasTarget - parentGasMetered)
num.Mul(num, parent.BaseFee)
num.Div(num, denom.SetUint64(parentGasTarget))
num.Div(num, denom.SetUint64(denominator))
Expand Down
69 changes: 43 additions & 26 deletions consensus/misc/eip1559/eip1559_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,19 @@ func config() *params.ChainConfig {
return config
}

var TestCanyonTime = uint64(10)
var TestHoloceneTime = uint64(12)
var TestJovianTime = uint64(14)
var (
testCanyonTime = uint64(10)
testHoloceneTime = uint64(12)
testJovianTime = uint64(14)
)

func opConfig() *params.ChainConfig {
config := copyConfig(params.TestChainConfig)
config.LondonBlock = big.NewInt(5)
eip1559DenominatorCanyon := uint64(250)
config.CanyonTime = &TestCanyonTime
config.HoloceneTime = &TestHoloceneTime
config.JovianTime = &TestJovianTime
config.CanyonTime = &testCanyonTime
config.HoloceneTime = &testHoloceneTime
config.JovianTime = &testJovianTime
config.Optimism = &params.OptimismConfig{
EIP1559Elasticity: 6,
EIP1559Denominator: 50,
Expand Down Expand Up @@ -227,59 +229,74 @@ func TestCalcBaseFeeOptimismHolocene(t *testing.T) {
// TestCalcBaseFeeJovian tests that the minimum base fee is enforced
// when the computed base fee is less than the minimum base fee,
// if the feature is active and not enforced otherwise.
// It also tests that the base fee udpate will take the DA footprint as stored
// in the blob gas used field into account if it is larger than the gas used
// field.
func TestCalcBaseFeeJovian(t *testing.T) {
parentGasLimit := uint64(30_000_000)
denom := uint64(50)
elasticity := uint64(3)
parentGasTarget := parentGasLimit / elasticity
const zeroParentBlobGasUsed = 0

preJovian := TestJovianTime - 1
postJovian := TestJovianTime
preJovian := testJovianTime - 1
postJovian := testJovianTime

tests := []struct {
parentBaseFee int64
parentGasUsed uint64
parentTime uint64
minBaseFee uint64
expectedBaseFee uint64
parentBaseFee int64
parentGasUsed uint64
parentBlobGasUsed uint64
parentTime uint64
minBaseFee uint64
expectedBaseFee uint64
}{
// Test 0: gas used is below target, and the new calculated base fee is very low.
// But since we are pre Jovian, we don't enforce the minBaseFee.
{1, parentGasLimit/elasticity - 1_000_000, preJovian, 1e9, 1},
{1, parentGasTarget - 1_000_000, zeroParentBlobGasUsed, preJovian, 1e9, 1},
// Test 1: gas used is exactly the target gas, but the base fee is set too low so
// the base fee is expected to be the minBaseFee
{1, parentGasLimit / elasticity, postJovian, 1e9, 1e9},
{1, parentGasTarget, zeroParentBlobGasUsed, postJovian, 1e9, 1e9},
// Test 2: gas used exceeds gas target, but the new calculated base fee is still
// too low so the base fee is expected to be the minBaseFee
{1, parentGasLimit/elasticity + 1_000_000, postJovian, 1e9, 1e9},
{1, parentGasTarget + 1_000_000, zeroParentBlobGasUsed, postJovian, 1e9, 1e9},
// Test 3: gas used exceeds gas target, but the new calculated base fee is higher
// than the minBaseFee, so don't enforce minBaseFee. See the calculation below:
// gasUsedDelta = gasUsed - parentGasTarget = 20_000_000 - 30_000_000 / 3 = 10_000_000
// 2e9 * 10_000_000 / 10_000_000 / 50 = 40_000_000
// 2e9 + 40_000_000 = 2_040_000_000, which is greater than minBaseFee
{2e9, parentGasLimit/elasticity + 10_000_000, postJovian, 1e9, 2_040_000_000},
{2e9, parentGasTarget + 10_000_000, zeroParentBlobGasUsed, postJovian, 1e9, 2_040_000_000},
// Test 4: gas used is below target, but the new calculated base fee is still
// too low so the base fee is expected to be the minBaseFee
{1, parentGasLimit/elasticity - 1_000_000, postJovian, 1e9, 1e9},
{1, parentGasTarget - 1_000_000, zeroParentBlobGasUsed, postJovian, 1e9, 1e9},
// Test 5: gas used is below target, and the new calculated base fee is higher
// than the minBaseFee, so don't enforce minBaseFee. See the calculation below:
// gasUsedDelta = gasUsed - parentGasTarget = 9_000_000 - 30_000_000 / 3 = -1_000_000
// 2_097_152 * -1_000_000 / 10_000_000 / 50 = -4194.304
// 2_097_152 - 4194.304 = 2_092_957.696, which is greater than minBaseFee
{2_097_152, parentGasLimit/elasticity - 1_000_000, postJovian, 2e6, 2_092_958},
{2_097_152, parentGasTarget - 1_000_000, zeroParentBlobGasUsed, postJovian, 2e6, 2_092_958},
// Test 6: parent base fee already at minimum, below target => no change
{1e4, parentGasLimit/elasticity - 1, postJovian, 1e4, 1e4},
{1e4, parentGasTarget - 1, zeroParentBlobGasUsed, postJovian, 1e4, 1e4},
// Test 7: parent base fee already at minimum, above target => small increase as usual
{1e4, parentGasLimit/elasticity + 1, postJovian, 1e4, 1e4 + 1},
{1e4, parentGasTarget + 1, zeroParentBlobGasUsed, postJovian, 1e4, 1e4 + 1},

// Test 8: Pre-Jovian: parent base fee already at minimum, gas used at target, blob gas used at limit
// => no increase, minBaseFee ignored, high blob gas used ignored
{1e4, parentGasTarget, parentGasLimit, preJovian, 1e6, 1e4},
// Test 9: parent base fee already at minimum, gas used at target, da footprint above target => small increase
{1e4, parentGasTarget, parentGasTarget + 1, postJovian, 1e4, 1e4 + 1},
// Test 10: Test 3, but with high blob gas used instead of gas used
{2e9, parentGasTarget, parentGasTarget + 10_000_000, postJovian, 1e9, 2_040_000_000},
}
for i, test := range tests {
testName := fmt.Sprintf("test %d", i)
t.Run(testName, func(t *testing.T) {
parent := &types.Header{
Number: common.Big32,
GasLimit: parentGasLimit,
GasUsed: test.parentGasUsed,
BaseFee: big.NewInt(test.parentBaseFee),
Time: test.parentTime,
Number: common.Big32,
GasLimit: parentGasLimit,
GasUsed: test.parentGasUsed,
BlobGasUsed: &test.parentBlobGasUsed,
BaseFee: big.NewInt(test.parentBaseFee),
Time: test.parentTime,
}
parent.Extra = EncodeOptimismExtraData(opConfig(), test.parentTime, denom, elasticity, &test.minBaseFee)
have, want := CalcBaseFee(opConfig(), parent, parent.Time+2), big.NewInt(int64(test.expectedBaseFee))
Expand Down
16 changes: 10 additions & 6 deletions consensus/misc/eip4844/eip4844.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,16 @@ func VerifyEIP4844Header(config *params.ChainConfig, parent, header *types.Heade
return errors.New("header is missing blobGasUsed")
}

// Verify that the blob gas used remains within reasonable limits.
if !config.IsOptimism() && *header.BlobGasUsed > bcfg.maxBlobGas() {
return fmt.Errorf("blob gas used %d exceeds maximum allowance %d", *header.BlobGasUsed, bcfg.maxBlobGas())
}
if *header.BlobGasUsed%params.BlobTxBlobGasPerBlob != 0 {
return fmt.Errorf("blob gas used %d not a multiple of blob gas per blob %d", header.BlobGasUsed, params.BlobTxBlobGasPerBlob)
// OP Stack sets a zero blobGasUsed pre-Jovian. Post-Jovian, it stores the DA footprint, which is
// probably not a multiple of [params.BlobTxBlobGasPerBlob].
if !config.IsOptimism() {
// Verify that the blob gas used remains within reasonable limits.
if *header.BlobGasUsed > bcfg.maxBlobGas() {
return fmt.Errorf("blob gas used %d exceeds maximum allowance %d", *header.BlobGasUsed, bcfg.maxBlobGas())
}
if *header.BlobGasUsed%params.BlobTxBlobGasPerBlob != 0 {
return fmt.Errorf("blob gas used %d not a multiple of blob gas per blob %d", header.BlobGasUsed, params.BlobTxBlobGasPerBlob)
}
}

// Verify the excessBlobGas is correct based on the parent header
Expand Down
19 changes: 18 additions & 1 deletion core/block_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
}

// Check blob gas usage.
if header.BlobGasUsed != nil {
if !v.config.IsOptimism() && header.BlobGasUsed != nil {
if want := *header.BlobGasUsed / params.BlobTxBlobGasPerBlob; uint64(blobs) != want { // div because the header is surely good vs the body might be bloated
return fmt.Errorf("blob gas used mismatch (header %v, calculated %v)", *header.BlobGasUsed, blobs*params.BlobTxBlobGasPerBlob)
}
Expand All @@ -116,6 +116,23 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
}
}

// OP Stack Jovian DA footprint block limit.
if v.config.IsDAFootprintBlockLimit(header.Time) {
if header.BlobGasUsed == nil {
return errors.New("nil blob gas used in post-Jovian block header, should store DA footprint")
}
blobGasUsed := *header.BlobGasUsed
daFootprint, err := types.CalcDAFootprint(block.Transactions())
if err != nil {
return fmt.Errorf("failed to calculate DA footprint: %w", err)
} else if blobGasUsed != daFootprint {
return fmt.Errorf("invalid DA footprint in blobGasUsed field (remote: %d local: %d)", blobGasUsed, daFootprint)
}
if daFootprint > block.GasLimit() {
return fmt.Errorf("DA footprint %d exceeds block gas limit %d", daFootprint, block.GasLimit())
}
}

// Ancestor block must be known.
if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) {
Expand Down
8 changes: 0 additions & 8 deletions core/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,14 +421,6 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
b.header.RequestsHash = &reqHash
}

if config.IsDAFootprintBlockLimit(b.header.Time) {
gasUsed, err := types.CalcGasUsedJovian(b.txs, b.header.GasUsed)
if err != nil {
panic(err)
}
b.header.GasUsed = gasUsed
}

Comment thread
geoknee marked this conversation as resolved.
body := types.Body{Transactions: b.txs, Uncles: b.uncles, Withdrawals: b.withdrawals}
block, err := b.engine.FinalizeAndAssemble(cm, b.header, statedb, &body, b.receipts)
if err != nil {
Expand Down
8 changes: 0 additions & 8 deletions core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,6 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
requests = [][]byte{}
}

if p.config.IsDAFootprintBlockLimit(block.Time()) {
gasUsed, err := types.CalcGasUsedJovian(block.Transactions(), *usedGas)
if err != nil {
return nil, fmt.Errorf("failed to calculate Jovian gas used: %w", err)
}
*usedGas = gasUsed
}

Comment thread
sebastianst marked this conversation as resolved.
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
p.chain.engine.Finalize(p.chain, header, tracingStateDB, block.Body())

Expand Down
1 change: 1 addition & 0 deletions core/types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ type Header struct {
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`

// BlobGasUsed was added by EIP-4844 and is ignored in legacy headers.
// OP Stack stores the DA footprint in this field starting with the Jovian fork.
BlobGasUsed *uint64 `json:"blobGasUsed" rlp:"optional"`

// ExcessBlobGas was added by EIP-4844 and is ignored in legacy headers.
Expand Down
21 changes: 9 additions & 12 deletions core/types/rollup_cost.go
Original file line number Diff line number Diff line change
Expand Up @@ -556,10 +556,11 @@ func ExtractDAFootprintGasScalar(data []byte) (uint16, error) {
return daFootprintGasScalar, nil
}

// CalcGasUsedJovian calculates the gas used for an OP Stack chain.
// Jovian introduces a DA footprint block limit, which potentially increases the gasUsed.
// CalcGasUsedJovian must not be called for pre-Jovian blocks.
func CalcGasUsedJovian(txs []*Transaction, evmGasUsed uint64) (uint64, error) {
// CalcDAFootprint calculates the total DA footprint of a block for an OP Stack chain.
// Jovian introduces a DA footprint block limit which is stored in the BlobGasUsed header field and that is taken
// into account during base fee updates.
// CalcDAFootprint must not be called for pre-Jovian blocks.
func CalcDAFootprint(txs []*Transaction) (uint64, error) {
if len(txs) == 0 || !txs[0].IsDepositTx() {
return 0, errors.New("missing deposit transaction")
}
Expand All @@ -572,25 +573,21 @@ func CalcGasUsedJovian(txs []*Transaction, evmGasUsed uint64) (uint64, error) {
// sufficient to check last transaction because deposits precede non-deposit txs
return 0, errors.New("unexpected non-deposit transactions in Jovian activation block")
}
return evmGasUsed, nil
return 0, nil
Comment thread
sebastianst marked this conversation as resolved.
} // ExtractDAFootprintGasScalar catches all invalid lengths

daFootprintGasScalar, err := ExtractDAFootprintGasScalar(data)
if err != nil {
return 0, err
}
var cumulativeDAFootprint uint64
var daFootprint uint64
for _, tx := range txs {
if tx.IsDepositTx() {
continue
}
cumulativeDAFootprint += tx.RollupCostData().EstimatedDASize().Uint64()
daFootprint += tx.RollupCostData().EstimatedDASize().Uint64() * uint64(daFootprintGasScalar)
}
daFootprint := uint64(daFootprintGasScalar) * cumulativeDAFootprint
if evmGasUsed < daFootprint {
return daFootprint, nil
}
return evmGasUsed, nil
return daFootprint, nil
Comment thread
sebastianst marked this conversation as resolved.
}

// L1Cost computes the the data availability fee for transactions in blocks prior to the Ecotone
Expand Down
Loading