Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 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
12 changes: 12 additions & 0 deletions op-chain-ops/genesis/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,18 @@ type UpgradeScheduleDeployConfig struct {
L1CancunTimeOffset *hexutil.Uint64 `json:"l1CancunTimeOffset,omitempty"`
// When Prague activates. Relative to L1 genesis.
L1PragueTimeOffset *hexutil.Uint64 `json:"l1PragueTimeOffset,omitempty"`
// When Osaka activates. Relative to L1 genesis.
L1OsakaTimeOffset *hexutil.Uint64 `json:"l1OsakaTimeOffset,omitempty"`
// When BPO1 activates. Relative to L1 genesis.
L1BPO1TimeOffset *hexutil.Uint64 `json:"l1BPO1TimeOffset,omitempty"`
// When BPO2 activates. Relative to L1 genesis.
L1BPO2TimeOffset *hexutil.Uint64 `json:"l1BPO2TimeOffset,omitempty"`
// When BPO3 activates. Relative to L1 genesis.
L1BPO3TimeOffset *hexutil.Uint64 `json:"l1BPO3TimeOffset,omitempty"`
// When BPO4 activates. Relative to L1 genesis.
L1BPO4TimeOffset *hexutil.Uint64 `json:"l1BPO4TimeOffset,omitempty"`
// Blob schedule config.
L1BlobScheduleConfig *params.BlobScheduleConfig `json:"l1BlobScheduleConfig,omitempty"`
}

var _ ConfigChecker = (*UpgradeScheduleDeployConfig)(nil)
Expand Down
41 changes: 41 additions & 0 deletions op-chain-ops/genesis/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ func NewL1Genesis(config *DeployConfig) (*core.Genesis, error) {
DevL1DeployConfig: config.DevL1DeployConfig,
L1ChainID: eth.ChainIDFromUInt64(config.L1ChainID),
L1PragueTimeOffset: (*uint64)(config.L1PragueTimeOffset),
L1OsakaTimeOffset: (*uint64)(config.L1OsakaTimeOffset),
L1BPO1TimeOffset: (*uint64)(config.L1BPO1TimeOffset),
L1BPO2TimeOffset: (*uint64)(config.L1BPO2TimeOffset),
L1BPO3TimeOffset: (*uint64)(config.L1BPO3TimeOffset),
L1BPO4TimeOffset: (*uint64)(config.L1BPO4TimeOffset),
BlobScheduleConfig: config.L1BlobScheduleConfig,
})
}

Expand All @@ -148,6 +154,18 @@ type DevL1DeployConfigMinimal struct {
L1ChainID eth.ChainID
// When Prague activates. Relative to L1 genesis.
L1PragueTimeOffset *uint64
// When Osaka activates. Relative to L1 genesis.
L1OsakaTimeOffset *uint64
// When BPO1 activates. Relative to L1 genesis.
L1BPO1TimeOffset *uint64
// When BPO2 activates. Relative to L1 genesis.
L1BPO2TimeOffset *uint64
// When BPO3 activates. Relative to L1 genesis.
L1BPO3TimeOffset *uint64
// When BPO4 activates. Relative to L1 genesis.
L1BPO4TimeOffset *uint64
// Blob schedule config.
BlobScheduleConfig *params.BlobScheduleConfig
}

// NewL1GenesisMinimal creates a L1 dev genesis template.
Expand Down Expand Up @@ -203,6 +221,29 @@ func NewL1GenesisMinimal(config *DevL1DeployConfigMinimal) (*core.Genesis, error
pragueTime := uint64(timestamp) + uint64(*config.L1PragueTimeOffset)
chainConfig.PragueTime = &pragueTime
}
if config.L1OsakaTimeOffset != nil {
osakaTime := uint64(timestamp) + uint64(*config.L1OsakaTimeOffset)
chainConfig.OsakaTime = &osakaTime
}
if config.L1BPO1TimeOffset != nil {
bpo1Time := uint64(timestamp) + uint64(*config.L1BPO1TimeOffset)
chainConfig.BPO1Time = &bpo1Time
}
if config.L1BPO2TimeOffset != nil {
bpo2Time := uint64(timestamp) + uint64(*config.L1BPO2TimeOffset)
chainConfig.BPO2Time = &bpo2Time
}
if config.L1BPO3TimeOffset != nil {
bpo3Time := uint64(timestamp) + uint64(*config.L1BPO3TimeOffset)
chainConfig.BPO3Time = &bpo3Time
}
if config.L1BPO4TimeOffset != nil {
bpo4Time := uint64(timestamp) + uint64(*config.L1BPO4TimeOffset)
chainConfig.BPO4Time = &bpo4Time
}
if config.BlobScheduleConfig != nil {
chainConfig.BlobScheduleConfig = config.BlobScheduleConfig
}
// Note: excess-blob-gas, blob-gas-used, withdrawals-hash, requests-hash are set to reasonable defaults for L1 by the ToBlock() function
return &core.Genesis{
Config: &chainConfig,
Expand Down
11 changes: 11 additions & 0 deletions op-e2e/actions/helpers/l1_miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,17 @@ func (s *L1Miner) ActEmptyBlock(t Testing) *types.Block {
return s.ActL1EndBlock(t)
}

func (s *L1Miner) ActBuildToOsaka(t Testing) *types.Block {
t.Helper()
require.NotNil(t, s.l1Cfg.Config.OsakaTime, "cannot activate OsakaTime when it is not scheduled")
h := s.L1Chain().CurrentHeader()
for h.Time < *s.l1Cfg.Config.OsakaTime {
h = s.ActEmptyBlock(t).Header()
}
require.True(t, s.l1Cfg.Config.IsOsaka(h.Number, h.Time), "Osaka not active at block", h.Number)
return s.L1Chain().GetBlockByHash(h.Hash())
}

func (s *L1Miner) Close() error {
return s.L1Replica.Close()
}
4 changes: 2 additions & 2 deletions op-e2e/actions/helpers/l2_sequencer.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,9 @@ func (s *L2Sequencer) ActL2ForceAdvanceL1Origin(t Testing) {
s.mockL1OriginSelector.originOverride = nextOrigin
}

// ActBuildToL1Head builds empty blocks until (incl.) the L1 head becomes the L2 origin
// ActBuildToL1Head builds empty blocks until (incl.) the L1 head becomes the L1 origin of the L2 head
func (s *L2Sequencer) ActBuildToL1Head(t Testing) {
for s.engine.UnsafeL2Head().L1Origin.Number < s.syncStatus.L1Head().Number {
for s.L2Unsafe().L1Origin.Number < s.syncStatus.L1Head().Number {
s.ActL2PipelineFull(t)
s.ActL2EmptyBlock(t)
}
Expand Down
141 changes: 141 additions & 0 deletions op-e2e/actions/proofs/l1_blob_parameter_forks_test.go
Comment thread
sebastianst marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package proofs_test

import (
"math/big"
"testing"

batcherFlags "github.com/ethereum-optimism/optimism/op-batcher/flags"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
actionsHelpers "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers"
"github.com/ethereum-optimism/optimism/op-e2e/actions/proofs/helpers"
legacybindings "github.com/ethereum-optimism/optimism/op-e2e/bindings"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/predeploys"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require"
)

// Test_ProgramAction_BlobParameterForks tests the blob base fee calculation for different forks.
func Test_ProgramAction_BlobParameterForks(gt *testing.T) {
runBlobParameterForksTest := func(gt *testing.T, testCfg *helpers.TestCfg[any]) {
t := actionsHelpers.NewDefaultTesting(gt)

// Create test environment with Fusaka activation
env := helpers.NewL2FaultProofEnv(t, testCfg, helpers.NewTestParams(),
helpers.NewBatcherCfg(
func(c *actionsHelpers.BatcherCfg) {
c.DataAvailabilityType = batcherFlags.BlobsType
},
),
func(dp *genesis.DeployConfig) {
dp.L1CancunTimeOffset = ptr(hexutil.Uint64(0))
dp.L1PragueTimeOffset = ptr(hexutil.Uint64(12))
dp.L1OsakaTimeOffset = ptr(hexutil.Uint64(24))
dp.L1BPO1TimeOffset = ptr(hexutil.Uint64(36))
dp.L1BPO2TimeOffset = ptr(hexutil.Uint64(48))
dp.L1BPO3TimeOffset = ptr(hexutil.Uint64(60))
dp.L1BPO4TimeOffset = ptr(hexutil.Uint64(72))
dp.L1BlobScheduleConfig = &params.BlobScheduleConfig{
Cancun: params.DefaultCancunBlobConfig,
Osaka: params.DefaultOsakaBlobConfig,
Prague: params.DefaultPragueBlobConfig,
BPO1: params.DefaultBPO1BlobConfig,
BPO2: params.DefaultBPO2BlobConfig,
BPO3: params.DefaultBPO3BlobConfig,
BPO4: params.DefaultBPO4BlobConfig,
}
dp.L1GenesisBlockExcessBlobGas = ptr(hexutil.Uint64(1e8)) // Jack up the blob market so we can test the blob fee calculation
Comment thread
geoknee marked this conversation as resolved.
},
)

miner, sequencer := env.Miner, env.Sequencer

// Bind to L1Block contract on L2
l1BlockContract, err := legacybindings.NewL1Block(predeploys.L1BlockAddr, env.Engine.EthClient())
require.NoError(t, err)

atBlockWithHash := func(hash common.Hash) *bind.CallOpts {
return &bind.CallOpts{
BlockHash: hash,
}
}

// requireConsistentBlobBaseFeeForFork requires the blob base fee to be consistent between
// the L1 Origin block (computed using the excess blob gas and l1 chain config)
// and the L1 Block contract on L2 (accessed with a contract method call), for a given fork predicate.
requireConsistentBlobBaseFeeForFork := func(t actionsHelpers.StatefulTesting, l2Block eth.L2BlockRef, expectActive bool, label string, isActive func(num *big.Int, time uint64) bool) {
bbfL2, err := l1BlockContract.BlobBaseFee(atBlockWithHash(l2Block.Hash))
require.NoError(t, err)

l1Origin := miner.L1Chain().GetHeaderByHash(l2Block.L1Origin.Hash)
if expectActive {
require.True(t, isActive(l1Origin.Number, l1Origin.Time), "%s not active at l1 origin %d, time %d", label, l1Origin.Number, l1Origin.Time)
} else {
require.False(t, isActive(l1Origin.Number, l1Origin.Time), "%s should not be active at l1 origin %d, time %d", label, l1Origin.Number, l1Origin.Time)
}
bbfL1 := eip4844.CalcBlobFee(env.Sd.L1Cfg.Config, l1Origin)

require.True(t, bbfL2.Cmp(bbfL1) == 0,
"%s: blob base fee does not match, bbfL2=%d, bbfL1=%d, l1BlockNum=%d, l2BlockNum=%d", label, bbfL2, bbfL1, l1Origin.Number, l2Block.Number)

require.True(t, bbfL2.Cmp(big.NewInt(1)) > 0,
"%s: blob base fee is unrealistically low and doesn't exercise the blob fee calculation", label)
}

// buildL1ToTime advances L1 with empty blocks until the given fork time.
buildL1ToTime := func(t actionsHelpers.StatefulTesting, forkTime *uint64) {
require.NotNil(t, forkTime, "fork time must be configured")
h := miner.L1Chain().CurrentHeader()
for h.Time < *forkTime {
h = miner.ActEmptyBlock(t).Header()
}
}

// Iterate through all forks and assert pre/post activation blob fees match expectations
cfg := env.Sd.L1Cfg.Config
forks := []struct {
label string
forkTime *uint64
isActive func(num *big.Int, time uint64) bool
}{
{"Prague", cfg.PragueTime, func(num *big.Int, time uint64) bool { return cfg.IsPrague(num, time) }},
{"Osaka", cfg.OsakaTime, func(num *big.Int, time uint64) bool { return cfg.IsOsaka(num, time) }},
{"BPO1", cfg.BPO1Time, func(num *big.Int, time uint64) bool { return cfg.IsBPO1(num, time) }},
{"BPO2", cfg.BPO2Time, func(num *big.Int, time uint64) bool { return cfg.IsBPO2(num, time) }},
{"BPO3", cfg.BPO3Time, func(num *big.Int, time uint64) bool { return cfg.IsBPO3(num, time) }},
{"BPO4", cfg.BPO4Time, func(num *big.Int, time uint64) bool { return cfg.IsBPO4(num, time) }},
}
for _, f := range forks {
// Advance L1 to fork activation
buildL1ToTime(t, f.forkTime)

// Build an empty L2 block which still has a pre-fork L1 origin, and check blob fee
sequencer.ActL2EmptyBlock(t)
l2Block := sequencer.SyncStatus().UnsafeL2
requireConsistentBlobBaseFeeForFork(t, l2Block, false, f.label, f.isActive)

// Advance L2 chain until L1 origin is at/after the fork activation
sequencer.ActL1HeadSignal(t)
sequencer.ActBuildToL1HeadUnsafe(t)

l2Block = sequencer.L2Unsafe()
require.Greater(t, l2Block.Number, uint64(1))
requireConsistentBlobBaseFeeForFork(t, l2Block, true, f.label, f.isActive)
}

// Final sync
env.BatchMineAndSync(t)

// Run fault proof program
safeL2Head := sequencer.L2Safe()
env.RunFaultProofProgramFromGenesis(t, safeL2Head.Number, testCfg.CheckResult, testCfg.InputParams...)
}

matrix := helpers.NewMatrix[any]()
defer matrix.Run(gt)
matrix.AddDefaultTestCases(nil, helpers.NewForkMatrix(helpers.LatestFork), runBlobParameterForksTest)
Comment thread
geoknee marked this conversation as resolved.
}
27 changes: 4 additions & 23 deletions op-e2e/actions/proofs/l1_prague_fork_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,8 @@ import (
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
actionsHelpers "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers"
"github.com/ethereum-optimism/optimism/op-e2e/actions/proofs/helpers"
legacybindings "github.com/ethereum-optimism/optimism/op-e2e/bindings"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/predeploys"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core/types"
"github.com/holiman/uint256"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -41,13 +37,11 @@ func Test_ProgramAction_PragueForkAfterGenesis(gt *testing.T) {
),
func(dp *genesis.DeployConfig) {
dp.L1PragueTimeOffset = ptr(hexutil.Uint64(24)) // Activate at second l1 block

Comment thread
geoknee marked this conversation as resolved.
Outdated
},
)

miner, batcher, verifier, sequencer, engine := env.Miner, env.Batcher, env.Sequencer, env.Sequencer, env.Engine

l1Block, err := legacybindings.NewL1Block(predeploys.L1BlockAddr, engine.EthClient())
require.NoError(t, err)
miner, batcher, verifier, sequencer, _ := env.Miner, env.Batcher, env.Sequencer, env.Sequencer, env.Engine

// utils
checkVerifierDerivedToL1Head := func(t actionsHelpers.StatefulTesting) {
Expand Down Expand Up @@ -89,17 +83,6 @@ func Test_ProgramAction_PragueForkAfterGenesis(gt *testing.T) {
checkVerifierDerivedToL1Head(t)
}

checkL1BlockBlobBaseFee := func(t actionsHelpers.StatefulTesting, l2Block eth.L2BlockRef) {
l1BlockID := l2Block.L1Origin
l1BlockHeader := miner.L1Chain().GetHeaderByHash(l1BlockID.Hash)
expectedBbf := eth.CalcBlobFeeDefault(l1BlockHeader)
upstreamExpectedBbf := eip4844.CalcBlobFee(env.Sd.L1Cfg.Config, l1BlockHeader)
require.Equal(t, expectedBbf.Uint64(), upstreamExpectedBbf.Uint64(), "expected blob base fee should match upstream calculation")
bbf, err := l1Block.BlobBaseFee(&bind.CallOpts{BlockHash: l2Block.Hash})
require.NoError(t, err, "failed to get blob base fee")
require.Equal(t, expectedBbf.Uint64(), bbf.Uint64(), "l1Block blob base fee does not match expectation, l1BlockNum %d, l2BlockNum %d", l1BlockID.Number, l2Block.Number)
}

requireSafeHeadProgression := func(t actionsHelpers.StatefulTesting, safeL2Before, safeL2After eth.L2BlockRef, batchedWithSetCodeTx bool) {
if batchedWithSetCodeTx {
require.Equal(t, safeL2Before, safeL2After, "safe head should not have changed (SetCode / type 4 batcher tx ignored)")
Expand Down Expand Up @@ -144,11 +127,10 @@ func Test_ProgramAction_PragueForkAfterGenesis(gt *testing.T) {
// Cache safe head before verifier sync
safeL2Initial := verifier.SyncStatus().SafeL2

// Build an empty L2 block which has a pre-prague L1 origin, and check the blob fee is correct
// Build an empty L2 block which has a pre-prague L1 origin
sequencer.ActL2EmptyBlock(t)
l1OriginHeader := miner.L1Chain().GetHeaderByHash(verifier.SyncStatus().UnsafeL2.L1Origin.Hash)
requirePragueStatusOnL1(false, l1OriginHeader)
checkL1BlockBlobBaseFee(t, verifier.SyncStatus().UnsafeL2)

// Build L2 unsafe chain and batch it to L1 using either DynamicFee or
// EIP-7702 SetCode txs
Expand All @@ -165,10 +147,9 @@ func Test_ProgramAction_PragueForkAfterGenesis(gt *testing.T) {

sequencer.ActBuildToL1Head(t) // Advance L2 chain until L1 origin has Prague active

// Check that the l1 origin is now a Prague block, and that the blob fee is correct
// Check that the l1 origin is now a Prague block
l1Origin := miner.L1Chain().GetHeaderByNumber(verifier.SyncStatus().UnsafeL2.L1Origin.Number)
requirePragueStatusOnL1(true, l1Origin)
checkL1BlockBlobBaseFee(t, verifier.SyncStatus().UnsafeL2)

// Batch and sync again
buildUnsafeL2AndSubmit(testCfg.Custom.useSetCodeTx)
Expand Down