diff --git a/op-acceptance-tests/tests/fusaka/fusaka_test.go b/op-acceptance-tests/tests/fusaka/fusaka_test.go index 94016a623273e..94d6b5ed5bcbc 100644 --- a/op-acceptance-tests/tests/fusaka/fusaka_test.go +++ b/op-acceptance-tests/tests/fusaka/fusaka_test.go @@ -3,6 +3,7 @@ package fusaka import ( "context" "crypto/rand" + "errors" "math/big" "sync" "testing" @@ -18,10 +19,10 @@ import ( "github.com/ethereum-optimism/optimism/op-service/txintent/bindings" "github.com/ethereum-optimism/optimism/op-service/txintent/contractio" "github.com/ethereum-optimism/optimism/op-service/txplan" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/params" ) func TestSafeHeadAdvancesAfterOsaka(gt *testing.T) { @@ -50,30 +51,10 @@ func TestBlobBaseFeeIsCorrectAfterBPOFork(gt *testing.T) { sys.L1EL.WaitForTime(*sys.L1Network.Escape().ChainConfig().BPO1Time) t.Log("BPO1 activated") - sys.L1EL.WaitForBlock() - l1BlockTime := sys.L1EL.EstimateBlockTime() - l1ChainConfig := sys.L1Network.Escape().ChainConfig() - spamBlobs(t, sys) // Raise the blob base fee to make blob parameter changes visible. - // Wait for the blob base fee to rise above 1 so the blob parameter changes will be visible. - for range time.Tick(l1BlockTime) { - info, _, err := sys.L1EL.EthClient().InfoAndTxsByLabel(t.Ctx(), eth.Unsafe) - t.Require().NoError(err) - if calcBlobBaseFee(l1ChainConfig, info).Cmp(big.NewInt(1)) > 0 { - break - } - t.Logf("Waiting for blob base fee to rise above 1") - } - - l2UnsafeRef := sys.L2CL.SyncStatus().UnsafeL2 - - // Get the L1 blob base fee. - l1OriginInfo, err := sys.L1EL.EthClient().InfoByHash(t.Ctx(), l2UnsafeRef.L1Origin.Hash) - t.Require().NoError(err) - l1BlobBaseFee := calcBlobBaseFee(l1ChainConfig, l1OriginInfo) - - l2Info, l2Txs, err := sys.L2EL.Escape().EthClient().InfoAndTxsByHash(t.Ctx(), l2UnsafeRef.Hash) + l2UnsafeHash, l1BlobBaseFee := waitForNonTrivialBPO1Block(t, sys) + l2Info, l2Txs, err := sys.L2EL.Escape().EthClient().InfoAndTxsByHash(t.Ctx(), l2UnsafeHash) t.Require().NoError(err) // Check the L1 blob base fee in the system deposit tx. @@ -91,6 +72,45 @@ func TestBlobBaseFeeIsCorrectAfterBPOFork(gt *testing.T) { t.Require().Equal(l1BlobBaseFee, l2BlobBaseFee) } +// waitForNonTrivialBPO1Block will return an L1 blob base fee that can only be calculated using the +// correct BPO1 parameters (i.e., the Osaka parameters result in a different value). It also +// returns an L2 block hash from the same epoch. +func waitForNonTrivialBPO1Block(t devtest.T, sys *presets.Minimal) (common.Hash, *big.Int) { + l1ChainConfig := sys.L1Network.Escape().ChainConfig() + l1BlockTime := sys.L1EL.EstimateBlockTime() + for { + l2UnsafeRef := sys.L2CL.SyncStatus().UnsafeL2 + + l1Info, _, err := sys.L1EL.EthClient().InfoAndTxsByHash(t.Ctx(), l2UnsafeRef.L1Origin.Hash) + if errors.Is(err, ethereum.NotFound) { // Possible reorg, try again. + continue + } + t.Require().NoError(err) + + // Calculate expected blob base fee with old Osaka parameters. + osakaBlobBaseFee := eip4844.CalcBlobFee(l1ChainConfig, &types.Header{ + Time: *l1ChainConfig.OsakaTime, + ExcessBlobGas: l1Info.ExcessBlobGas(), + }) + + // Calculate expected blob base fee with new BPO1 parameters. + bpo1BlobBaseFee := eip4844.CalcBlobFee(l1ChainConfig, &types.Header{ + Time: l1Info.Time(), + ExcessBlobGas: l1Info.ExcessBlobGas(), + }) + + if bpo1BlobBaseFee.Cmp(osakaBlobBaseFee) != 0 { + return l2UnsafeRef.Hash, bpo1BlobBaseFee + } + + select { + case <-t.Ctx().Done(): + t.Require().Fail("context canceled before finding a block with a divergent base fee") + case <-time.After(l1BlockTime): + } + } +} + func spamBlobs(t devtest.T, sys *presets.Minimal) { l1BlockTime := sys.L1EL.EstimateBlockTime() l1ChainConfig := sys.L1Network.Escape().ChainConfig() @@ -134,12 +154,3 @@ func spamBlobs(t devtest.T, sys *presets.Minimal) { schedule.Run(t.WithCtx(ctx), spammer) }() } - -func calcBlobBaseFee(cfg *params.ChainConfig, info eth.BlockInfo) *big.Int { - return eip4844.CalcBlobFee(cfg, &types.Header{ - // It's unfortunate that we can't build a proper header from a BlockInfo. - // We do our best to work around deficiencies in the BlockInfo implementation here. - Time: info.Time(), - ExcessBlobGas: info.ExcessBlobGas(), - }) -} diff --git a/op-acceptance-tests/tests/fusaka/init_test.go b/op-acceptance-tests/tests/fusaka/init_test.go index 158831fffa162..8b85481cccfbf 100644 --- a/op-acceptance-tests/tests/fusaka/init_test.go +++ b/op-acceptance-tests/tests/fusaka/init_test.go @@ -63,7 +63,9 @@ func TestMain(m *testing.M) { sysgo.WithDeployerOptions(func(_ devtest.P, _ devkeys.Keys, builder intentbuilder.Builder) { _, l1Config := builder.WithL1(sysgo.DefaultL1ID) l1Config.WithOsakaOffset(0) - l1Config.WithBPO1Offset(0) + // Make the BPO fork happen after Osaka so we can easily use geth's eip4844.CalcBlobFee + // to calculate the blob base fee using the Osaka parameters. + l1Config.WithBPO1Offset(1) l1Config.WithL1BlobSchedule(¶ms.BlobScheduleConfig{ Cancun: params.DefaultCancunBlobConfig, Osaka: params.DefaultOsakaBlobConfig, diff --git a/op-devstack/dsl/l1_el.go b/op-devstack/dsl/l1_el.go index da6af4eb4dba4..207c8fb2e5759 100644 --- a/op-devstack/dsl/l1_el.go +++ b/op-devstack/dsl/l1_el.go @@ -42,10 +42,19 @@ func (el *L1ELNode) EthClient() apis.EthClient { // EstimateBlockTime estimates the L1 block based on the last 1000 blocks // (or since genesis, if insufficient blocks). func (el *L1ELNode) EstimateBlockTime() time.Duration { - latest, err := el.inner.EthClient().BlockRefByLabel(el.t.Ctx(), eth.Unsafe) - el.require.NoError(err) - if latest.Number == 0 { - return time.Second * 12 + var latest eth.BlockRef + for { + var err error + latest, err = el.inner.EthClient().BlockRefByLabel(el.t.Ctx(), eth.Unsafe) + el.require.NoError(err) + if latest.Number > 0 { + break + } + select { + case <-time.After(time.Millisecond * 500): + case <-el.ctx.Done(): + el.require.Fail("context was canceled before L1 block time could be estimated") + } } lowerNum := uint64(0) if latest.Number > 1000 {