Skip to content

Commit

Permalink
Add support for the new Isthmus block info tx
Browse files Browse the repository at this point in the history
  • Loading branch information
mdehoog committed Jan 2, 2025
1 parent 928070c commit dc3e6fe
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 32 deletions.
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}
// IsthmusL1AttributesSelector is the selector indicating Isthmus style L1 gas attributes.
// keccak256("setL1BlockValuesIsthmus()")[:4]
IsthmusL1AttributesSelector = [4]byte{0x09, 0x89, 0x99, 0xbe}
// 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 + Isthmus, 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 IsthmusL1AttributesSelector:
if !config.IsIsthmus(time) {
return gasParams{}, fmt.Errorf("setL1BlockValuesIsthmus called before Isthmus active")
}
p, err = extractL1GasParamsPostIsthmus(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 Isthmus
p, err = extractL1GasParamsPostIsthmus(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 extractL1GasParamsPostEcotoneIsthmus(data)
}

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

func extractL1GasParamsPostEcotoneIsthmus(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,
// Isthmus 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 TestExtractIsthmusGasParams(t *testing.T) {
zeroTime := uint64(0)
// create a config where isthmus is active
config := &params.ChainConfig{
Optimism: params.OptimismTestConfig.Optimism,
RegolithTime: &zeroTime,
EcotoneTime: &zeroTime,
FjordTime: &zeroTime,
IsthmusTime: &zeroTime,
}
require.True(t, config.IsOptimismIsthmus(zeroTime))

data := getIsthmusL1Attributes(
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 isthmus upgrade is properly detected, and invokes the ecotone
// cost function appropriately
func TestFirstBlockIsthmusGasParams(t *testing.T) {
zeroTime := uint64(0)
// create a config where isthmus upgrade is active
config := &params.ChainConfig{
Optimism: params.OptimismTestConfig.Optimism,
RegolithTime: &zeroTime,
EcotoneTime: &zeroTime,
FjordTime: &zeroTime,
IsthmusTime: &zeroTime,
}
require.True(t, config.IsOptimismEcotone(0))
require.True(t, config.IsOptimismIsthmus(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 getIsthmusL1Attributes(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, IsthmusL1AttributesSelector[:]...)
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

0 comments on commit dc3e6fe

Please sign in to comment.