Skip to content

Add support for the new Jovian block info tx #457

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

Draft
wants to merge 2 commits into
base: optimism
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
107 changes: 77 additions & 30 deletions core/types/rollup_cost.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,18 @@ func init() {
}

var (
// BedrockL1AttributesSelector is the function selector indicating Bedrock style L1 gas
// attributes.
BedrockL1AttributesSelector = []byte{0x01, 0x5d, 0x8e, 0xb9}
// BedrockL1AttributesSelector is the function selector indicating Bedrock style L1 gas attributes.
// keccak256("setL1BlockValues(uint64,uint64,uint256,bytes32,uint64,bytes32,uint256,uint256)")[:4]
BedrockL1AttributesSelector = [4]byte{0x01, 0x5d, 0x8e, 0xb9}
// EcotoneL1AttributesSelector is the selector indicating Ecotone style L1 gas attributes.
EcotoneL1AttributesSelector = []byte{0x44, 0x0a, 0x5e, 0x20}

// keccak256("setL1BlockValuesEcotone()")[:4]
EcotoneL1AttributesSelector = [4]byte{0x44, 0x0a, 0x5e, 0x20}
// JovianL1AttributesSelector is the selector indicating Jovian style L1 gas attributes.
// keccak256("setL1BlockValuesJovian()")[:4]
JovianL1AttributesSelector = [4]byte{0x3d, 0xb6, 0xbe, 0x2b}
// InteropL1AttributesSelector is the selector indicating Interop style L1 gas attributes.
// keccak256("setL1BlockValuesInterop()")[:4]
InteropL1AttributesSelector = [4]byte{0x76, 0x0e, 0xe0, 0x4d}
// L1BlockAddr is the address of the L1Block contract which stores the L1 gas attributes.
L1BlockAddr = common.HexToAddress("0x4200000000000000000000000000000000000015")

Expand Down Expand Up @@ -259,35 +265,60 @@ func intToScaledFloat(scalar *big.Int) *big.Float {

// extractL1GasParams extracts the gas parameters necessary to compute gas costs from L1 block info
func extractL1GasParams(config *params.ChainConfig, time uint64, data []byte) (gasParams, error) {
// edge case: for the very first Ecotone block we still need to use the Bedrock
// function. We detect this edge case by seeing if the function selector is the old one
// If so, fall through to the pre-ecotone format
// Both Ecotone and Fjord use the same function selector
if config.IsEcotone(time) && len(data) >= 4 && !bytes.Equal(data[0:4], BedrockL1AttributesSelector) {
p, err := extractL1GasParamsPostEcotone(data)
if err != nil {
return gasParams{}, err
}
if len(data) < 4 {
return gasParams{}, fmt.Errorf("unexpected L1 info data length, got %d", len(data))
}

if config.IsFjord(time) {
p.costFunc = NewL1CostFuncFjord(
p.l1BaseFee,
p.l1BlobBaseFee,
big.NewInt(int64(*p.l1BaseFeeScalar)),
big.NewInt(int64(*p.l1BlobBaseFeeScalar)),
)
} else {
p.costFunc = newL1CostFuncEcotone(
p.l1BaseFee,
p.l1BlobBaseFee,
big.NewInt(int64(*p.l1BaseFeeScalar)),
big.NewInt(int64(*p.l1BlobBaseFeeScalar)),
)
var p gasParams
var err error
var signature [4]byte
copy(signature[:], data)
// Note: for Ecotone + Jovian, the new L1Block method selector is used in the block after
// activation, so we use the selector for the switch block rather than the fork time.
switch signature {
case BedrockL1AttributesSelector:
return extractL1GasParamsPreEcotone(config, time, data)
case EcotoneL1AttributesSelector:
if !config.IsEcotone(time) {
return gasParams{}, fmt.Errorf("setL1BlockValuesEcotone called before Ecotone active")
}
p, err = extractL1GasParamsPostEcotone(data)
case JovianL1AttributesSelector:
if !config.IsJovian(time) {
return gasParams{}, fmt.Errorf("setL1BlockValuesJovian called before Jovian active")
}
p, err = extractL1GasParamsPostJovian(data)
case InteropL1AttributesSelector:
if !config.IsInterop(time) {
return gasParams{}, fmt.Errorf("setL1BlockValuesInterop called before Interop active")
}
// Interop uses the same tx calldata size/format as Jovian
p, err = extractL1GasParamsPostJovian(data)
default:
return gasParams{}, fmt.Errorf("unknown L1Block function signature: 0x%s", common.Bytes2Hex(signature[:]))
}

return p, nil
if err != nil {
return gasParams{}, err
}
return extractL1GasParamsPreEcotone(config, time, data)

if config.IsFjord(time) {
p.costFunc = NewL1CostFuncFjord(
p.l1BaseFee,
p.l1BlobBaseFee,
big.NewInt(int64(*p.l1BaseFeeScalar)),
big.NewInt(int64(*p.l1BlobBaseFeeScalar)),
)
} else {
p.costFunc = newL1CostFuncEcotone(
p.l1BaseFee,
p.l1BlobBaseFee,
big.NewInt(int64(*p.l1BaseFeeScalar)),
big.NewInt(int64(*p.l1BlobBaseFeeScalar)),
)
}

return p, nil
}

func extractL1GasParamsPreEcotone(config *params.ChainConfig, time uint64, data []byte) (gasParams, error) {
Expand All @@ -314,6 +345,19 @@ func extractL1GasParamsPostEcotone(data []byte) (gasParams, error) {
if len(data) != 164 {
return gasParams{}, fmt.Errorf("expected 164 L1 info bytes, got %d", len(data))
}
return extractL1GasParamsPostEcotoneJovian(data)
}

// extractL1GasParamsPostJovian extracts the gas parameters necessary to compute gas from L1 attribute
// info calldata after the Jovian upgrade, but not for the very first Jovian block.
func extractL1GasParamsPostJovian(data []byte) (gasParams, error) {
if len(data) != 180 {
return gasParams{}, fmt.Errorf("expected 180 L1 info bytes, got %d", len(data))
}
return extractL1GasParamsPostEcotoneJovian(data)
}

func extractL1GasParamsPostEcotoneJovian(data []byte) (gasParams, error) {
// data layout assumed for Ecotone:
// offset type varname
// 0 <selector>
Expand All @@ -326,6 +370,9 @@ func extractL1GasParamsPostEcotone(data []byte) (gasParams, error) {
// 68 uint256 _blobBaseFee,
// 100 bytes32 _hash,
// 132 bytes32 _batcherHash,
// Jovian adds two more uint64s, which are ignored by this function:
// 164 uint64 _depositNonce
// 172 uint64 _configUpdateNonce
l1BaseFee := new(big.Int).SetBytes(data[36:68])
l1BlobBaseFee := new(big.Int).SetBytes(data[68:100])
l1BaseFeeScalar := binary.BigEndian.Uint32(data[4:8])
Expand Down
84 changes: 82 additions & 2 deletions core/types/rollup_cost_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,35 @@ func TestExtractFjordGasParams(t *testing.T) {
require.Equal(t, fjordFee, c)
}

func TestExtractJovianGasParams(t *testing.T) {
zeroTime := uint64(0)
// create a config where jovian is active
config := &params.ChainConfig{
Optimism: params.OptimismTestConfig.Optimism,
RegolithTime: &zeroTime,
EcotoneTime: &zeroTime,
FjordTime: &zeroTime,
JovianTime: &zeroTime,
}
require.True(t, config.IsOptimismJovian(zeroTime))

data := getJovianL1Attributes(
baseFee,
blobBaseFee,
baseFeeScalar,
blobBaseFeeScalar,
)

gasparams, err := extractL1GasParams(config, zeroTime, data)
require.NoError(t, err)
costFunc := gasparams.costFunc

c, g := costFunc(emptyTx.RollupCostData())

require.Equal(t, minimumFjordGas, g)
require.Equal(t, fjordFee, c)
}

// make sure the first block of the ecotone upgrade is properly detected, and invokes the bedrock
// cost function appropriately
func TestFirstBlockEcotoneGasParams(t *testing.T) {
Expand All @@ -228,11 +257,41 @@ func TestFirstBlockEcotoneGasParams(t *testing.T) {
require.Equal(t, regolithFee, c)
}

// make sure the first block of the jovian upgrade is properly detected, and invokes the ecotone
// cost function appropriately
func TestFirstBlockJovianGasParams(t *testing.T) {
zeroTime := uint64(0)
// create a config where jovian upgrade is active
config := &params.ChainConfig{
Optimism: params.OptimismTestConfig.Optimism,
RegolithTime: &zeroTime,
EcotoneTime: &zeroTime,
FjordTime: &zeroTime,
JovianTime: &zeroTime,
}
require.True(t, config.IsOptimismEcotone(0))
require.True(t, config.IsOptimismJovian(0))

data := getEcotoneL1Attributes(
baseFee,
blobBaseFee,
baseFeeScalar,
blobBaseFeeScalar,
)

gasparams, err := extractL1GasParams(config, zeroTime, data)
require.NoError(t, err)
oldCostFunc := gasparams.costFunc
c, g := oldCostFunc(emptyTx.RollupCostData())
require.Equal(t, minimumFjordGas, g)
require.Equal(t, fjordFee, c)
}

func getBedrockL1Attributes(baseFee, overhead, scalar *big.Int) []byte {
uint256 := make([]byte, 32)
ignored := big.NewInt(1234)
data := []byte{}
data = append(data, BedrockL1AttributesSelector...)
data = append(data, BedrockL1AttributesSelector[:]...)
data = append(data, ignored.FillBytes(uint256)...) // arg 0
data = append(data, ignored.FillBytes(uint256)...) // arg 1
data = append(data, baseFee.FillBytes(uint256)...) // arg 2
Expand All @@ -250,7 +309,26 @@ func getEcotoneL1Attributes(baseFee, blobBaseFee, baseFeeScalar, blobBaseFeeScal
uint256Slice := make([]byte, 32)
uint64Slice := make([]byte, 8)
uint32Slice := make([]byte, 4)
data = append(data, EcotoneL1AttributesSelector...)
data = append(data, EcotoneL1AttributesSelector[:]...)
data = append(data, baseFeeScalar.FillBytes(uint32Slice)...)
data = append(data, blobBaseFeeScalar.FillBytes(uint32Slice)...)
data = append(data, ignored.FillBytes(uint64Slice)...)
data = append(data, ignored.FillBytes(uint64Slice)...)
data = append(data, ignored.FillBytes(uint64Slice)...)
data = append(data, baseFee.FillBytes(uint256Slice)...)
data = append(data, blobBaseFee.FillBytes(uint256Slice)...)
data = append(data, ignored.FillBytes(uint256Slice)...)
data = append(data, ignored.FillBytes(uint256Slice)...)
return data
}

func getJovianL1Attributes(baseFee, blobBaseFee, baseFeeScalar, blobBaseFeeScalar *big.Int) []byte {
ignored := big.NewInt(1234)
data := []byte{}
uint256Slice := make([]byte, 32)
uint64Slice := make([]byte, 8)
uint32Slice := make([]byte, 4)
data = append(data, JovianL1AttributesSelector[:]...)
data = append(data, baseFeeScalar.FillBytes(uint32Slice)...)
data = append(data, blobBaseFeeScalar.FillBytes(uint32Slice)...)
data = append(data, ignored.FillBytes(uint64Slice)...)
Expand All @@ -260,6 +338,8 @@ func getEcotoneL1Attributes(baseFee, blobBaseFee, baseFeeScalar, blobBaseFeeScal
data = append(data, blobBaseFee.FillBytes(uint256Slice)...)
data = append(data, ignored.FillBytes(uint256Slice)...)
data = append(data, ignored.FillBytes(uint256Slice)...)
data = append(data, ignored.FillBytes(uint64Slice)...)
data = append(data, ignored.FillBytes(uint64Slice)...)
return data
}

Expand Down