Skip to content

Commit 7acb012

Browse files
committed
op-acceptance-tests: harden the BPO blob base fee calculation test
Before, we merely waited for the blob base fee to rise above 1 before assuming that the blob parameters would have a visible impact on the fee. Due to rounding, it is possible that different parameters still result in the same base fee even above the minimum (either 1 or the reserve price set by EIP-7918). Calculating the precise intersections between different blob base fee formulae is complicated, so we opt for the simpler but maximally robust approach: calculate both the BPO1 base fee and the Osaka base fee, and only proceed when they differ. This resolves a nit on a previous PR that was merged due to time pressure: #17529 (comment)
1 parent c0829ab commit 7acb012

File tree

3 files changed

+63
-38
lines changed

3 files changed

+63
-38
lines changed

op-acceptance-tests/tests/fusaka/fusaka_test.go

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package fusaka
33
import (
44
"context"
55
"crypto/rand"
6+
"errors"
67
"math/big"
78
"sync"
89
"testing"
@@ -18,10 +19,10 @@ import (
1819
"github.com/ethereum-optimism/optimism/op-service/txintent/bindings"
1920
"github.com/ethereum-optimism/optimism/op-service/txintent/contractio"
2021
"github.com/ethereum-optimism/optimism/op-service/txplan"
22+
"github.com/ethereum/go-ethereum"
2123
"github.com/ethereum/go-ethereum/common"
2224
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
2325
"github.com/ethereum/go-ethereum/core/types"
24-
"github.com/ethereum/go-ethereum/params"
2526
)
2627

2728
func TestSafeHeadAdvancesAfterOsaka(gt *testing.T) {
@@ -50,46 +51,68 @@ func TestBlobBaseFeeIsCorrectAfterBPOFork(gt *testing.T) {
5051
sys.L1EL.WaitForTime(*sys.L1Network.Escape().ChainConfig().BPO1Time)
5152
t.Log("BPO1 activated")
5253

53-
sys.L1EL.WaitForBlock()
54-
l1BlockTime := sys.L1EL.EstimateBlockTime()
55-
l1ChainConfig := sys.L1Network.Escape().ChainConfig()
56-
5754
spamBlobs(t, sys) // Raise the blob base fee to make blob parameter changes visible.
5855

59-
// Wait for the blob base fee to rise above 1 so the blob parameter changes will be visible.
60-
for range time.Tick(l1BlockTime) {
61-
info, _, err := sys.L1EL.EthClient().InfoAndTxsByLabel(t.Ctx(), eth.Unsafe)
62-
t.Require().NoError(err)
63-
if calcBlobBaseFee(l1ChainConfig, info).Cmp(big.NewInt(1)) > 0 {
64-
break
65-
}
66-
t.Logf("Waiting for blob base fee to rise above 1")
67-
}
68-
69-
l2UnsafeRef := sys.L2CL.SyncStatus().UnsafeL2
70-
71-
// Get the L1 blob base fee.
72-
l1OriginInfo, err := sys.L1EL.EthClient().InfoByHash(t.Ctx(), l2UnsafeRef.L1Origin.Hash)
56+
l2UnsafeHash, l1BlobBaseFee := waitForBlockWithDivergentBlobBaseFee(t, sys)
57+
l2BlockInfo, l2Txs, err := sys.L2EL.Escape().EthClient().InfoAndTxsByHash(t.Ctx(), l2UnsafeHash)
7358
t.Require().NoError(err)
74-
l1BlobBaseFee := calcBlobBaseFee(l1ChainConfig, l1OriginInfo)
7559

7660
// Get the L1 blob base fee from the system deposit tx.
77-
info, txs, err := sys.L2EL.Escape().EthClient().InfoAndTxsByHash(t.Ctx(), l2UnsafeRef.Hash)
78-
t.Require().NoError(err)
79-
blockInfo, err := derive.L1BlockInfoFromBytes(sys.L2Chain.Escape().RollupConfig(), info.Time(), txs[0].Data())
61+
blockInfo, err := derive.L1BlockInfoFromBytes(sys.L2Chain.Escape().RollupConfig(), l2BlockInfo.Time(), l2Txs[0].Data())
8062
t.Require().NoError(err)
8163
l2BlobBaseFee := blockInfo.BlobBaseFee
8264

8365
t.Require().Equal(l1BlobBaseFee, l2BlobBaseFee)
8466

8567
// Get the L1 Blob base fee from the L1Block contract.
8668
l1Block := bindings.NewL1Block(bindings.WithClient(sys.L2EL.Escape().EthClient()), bindings.WithTo(predeploys.L1BlockAddr))
87-
l2BlobBaseFee, err = contractio.Read(l1Block.BlobBaseFee(), t.Ctx())
69+
l2BlobBaseFee, err = contractio.Read(l1Block.BlobBaseFee(), t.Ctx(), func(tx *txplan.PlannedTx) {
70+
tx.AgainstBlock.Set(l2BlockInfo)
71+
})
8872
t.Require().NoError(err)
8973

9074
t.Require().Equal(l1BlobBaseFee, l2BlobBaseFee)
9175
}
9276

77+
// waitForBlockWithDivergentBlobBaseFee will return an L1 blob base fee that can only be calculated
78+
// using the correct BPO1 parameters (i.e., the Osaka parameters result in a different value). It
79+
// also returns an L2 block hash from the same epoch.
80+
func waitForBlockWithDivergentBlobBaseFee(t devtest.T, sys *presets.Minimal) (common.Hash, *big.Int) {
81+
l1ChainConfig := sys.L1Network.Escape().ChainConfig()
82+
l1BlockTime := sys.L1EL.EstimateBlockTime()
83+
for {
84+
select {
85+
case <-t.Ctx().Done():
86+
t.Require().Fail("context canceled before finding a block with a divergent base fee")
87+
case <-time.After(l1BlockTime):
88+
}
89+
90+
l2UnsafeRef := sys.L2CL.SyncStatus().UnsafeL2
91+
92+
l1Info, _, err := sys.L1EL.EthClient().InfoAndTxsByHash(t.Ctx(), l2UnsafeRef.L1Origin.Hash)
93+
if errors.Is(err, ethereum.NotFound) { // Possible reorg, try again.
94+
continue
95+
}
96+
t.Require().NoError(err)
97+
98+
// Calculate expected blob base fee with old Osaka parameters.
99+
osakaBlobBaseFee := eip4844.CalcBlobFee(l1ChainConfig, &types.Header{
100+
Time: *l1ChainConfig.OsakaTime,
101+
ExcessBlobGas: l1Info.ExcessBlobGas(),
102+
})
103+
104+
// Calculate expected blob base fee with new BPO1 parameters.
105+
bpo1BlobBaseFee := eip4844.CalcBlobFee(l1ChainConfig, &types.Header{
106+
Time: l1Info.Time(),
107+
ExcessBlobGas: l1Info.ExcessBlobGas(),
108+
})
109+
110+
if bpo1BlobBaseFee.Cmp(osakaBlobBaseFee) != 0 {
111+
return l2UnsafeRef.Hash, bpo1BlobBaseFee
112+
}
113+
}
114+
}
115+
93116
func spamBlobs(t devtest.T, sys *presets.Minimal) {
94117
l1BlockTime := sys.L1EL.EstimateBlockTime()
95118
l1ChainConfig := sys.L1Network.Escape().ChainConfig()
@@ -133,12 +156,3 @@ func spamBlobs(t devtest.T, sys *presets.Minimal) {
133156
schedule.Run(t.WithCtx(ctx), spammer)
134157
}()
135158
}
136-
137-
func calcBlobBaseFee(cfg *params.ChainConfig, info eth.BlockInfo) *big.Int {
138-
return eip4844.CalcBlobFee(cfg, &types.Header{
139-
// It's unfortunate that we can't build a proper header from a BlockInfo.
140-
// We do our best to work around deficiencies in the BlockInfo implementation here.
141-
Time: info.Time(),
142-
ExcessBlobGas: info.ExcessBlobGas(),
143-
})
144-
}

op-acceptance-tests/tests/fusaka/init_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ func TestMain(m *testing.M) {
6363
sysgo.WithDeployerOptions(func(_ devtest.P, _ devkeys.Keys, builder intentbuilder.Builder) {
6464
_, l1Config := builder.WithL1(sysgo.DefaultL1ID)
6565
l1Config.WithOsakaOffset(0)
66-
l1Config.WithBPO1Offset(0)
66+
// Make the BPO fork happen after Osaka so we can easily use geth's eip4844.CalcBlobFee
67+
// to calculate the blob base fee using the Osaka parameters.
68+
l1Config.WithBPO1Offset(1)
6769
l1Config.WithL1BlobSchedule(&params.BlobScheduleConfig{
6870
Cancun: params.DefaultCancunBlobConfig,
6971
Osaka: params.DefaultOsakaBlobConfig,

op-devstack/dsl/l1_el.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,19 @@ func (el *L1ELNode) EthClient() apis.EthClient {
4242
// EstimateBlockTime estimates the L1 block based on the last 1000 blocks
4343
// (or since genesis, if insufficient blocks).
4444
func (el *L1ELNode) EstimateBlockTime() time.Duration {
45-
latest, err := el.inner.EthClient().BlockRefByLabel(el.t.Ctx(), eth.Unsafe)
46-
el.require.NoError(err)
47-
if latest.Number == 0 {
48-
return time.Second * 12
45+
var latest eth.BlockRef
46+
for {
47+
var err error
48+
latest, err = el.inner.EthClient().BlockRefByLabel(el.t.Ctx(), eth.Unsafe)
49+
el.require.NoError(err)
50+
if latest.Number > 0 {
51+
break
52+
}
53+
select {
54+
case <-time.After(time.Millisecond * 500):
55+
case <-el.ctx.Done():
56+
el.require.Fail("context was canceled before L1 block time could be estimated")
57+
}
4958
}
5059
lowerNum := uint64(0)
5160
if latest.Number > 1000 {

0 commit comments

Comments
 (0)