Skip to content
Merged
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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ require (
lukechampine.com/blake3 v1.3.0 // indirect
)

replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101603.2-0.20251016091451-5c6d276814f2
replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101603.2-0.20251017174928-6658a36c9862

// replace github.com/ethereum/go-ethereum => ../op-geth

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,8 @@ github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A=
github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.4-0.20251001155152-4eb15ccedf7e h1:iy1vBIzACYUyOVyoADUwvAiq2eOPC0yVsDUdolPwQjk=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.4-0.20251001155152-4eb15ccedf7e/go.mod h1:DYj7+vYJ4cIB7zera9mv4LcAynCL5u4YVfoeUu6Wa+w=
github.com/ethereum-optimism/op-geth v1.101603.2-0.20251016091451-5c6d276814f2 h1:fvYTR+KOcvSDd/gJuh+ALG/Fx7Y0xU3ZaDkgT/kqVi0=
github.com/ethereum-optimism/op-geth v1.101603.2-0.20251016091451-5c6d276814f2/go.mod h1:Ct2QjqZ2UKgvvgKLLYzoh/DBicJZB8DXsv45DgEjcco=
github.com/ethereum-optimism/op-geth v1.101603.2-0.20251017174928-6658a36c9862 h1:15VLb6evTBOzznvwZxesXxLFI34gHjFfIlU+Oaga3sc=
github.com/ethereum-optimism/op-geth v1.101603.2-0.20251017174928-6658a36c9862/go.mod h1:Ct2QjqZ2UKgvvgKLLYzoh/DBicJZB8DXsv45DgEjcco=
github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20251009180028-9b4658b9b7af h1:WWz0gJM/boaUImtJnROecPirAerKCLpAU4m6Tx0ArOg=
github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20251009180028-9b4658b9b7af/go.mod h1:NZ816PzLU1TLv1RdAvYAb6KWOj4Zm5aInT0YpDVml2Y=
github.com/ethereum/c-kzg-4844/v2 v2.1.0 h1:gQropX9YFBhl3g4HYhwE70zq3IHFRgbbNPw0Shwzf5w=
Expand Down
234 changes: 185 additions & 49 deletions op-acceptance-tests/tests/jovian/da_footprint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,25 @@ package jovian
import (
"context"
"crypto/rand"
"math/big"
"sync"
"testing"
"time"

"github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/interop/loadtest"
"github.com/ethereum-optimism/optimism/op-chain-ops/devkeys"
"github.com/ethereum-optimism/optimism/op-devstack/devtest"
"github.com/ethereum-optimism/optimism/op-devstack/dsl"
"github.com/ethereum-optimism/optimism/op-devstack/presets"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/txinclude"
"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/common"
"github.com/ethereum/go-ethereum/core/types"
)

type CalldataSpammer struct {
Expand All @@ -34,62 +42,190 @@ func (s *CalldataSpammer) Spam(t devtest.T) error {
return err
}

type daFootprintSystemConfig struct {
SetDAFootprintGasScalar func(scalar uint16) bindings.TypedCall[any] `sol:"setDAFootprintGasScalar"`
DAFootprintGasScalar func() bindings.TypedCall[uint16] `sol:"daFootprintGasScalar"`
}

type daFootprintL1Block struct {
DAFootprintGasScalar func() bindings.TypedCall[uint16] `sol:"daFootprintGasScalar"`
}

type daFootprintEnv struct {
l1Client *dsl.L1ELNode
l2Network *dsl.L2Network
l2EL *dsl.L2ELNode
systemConfig daFootprintSystemConfig
l1Block daFootprintL1Block
}

func newDAFootprintEnv(t devtest.T, l2Network *dsl.L2Network, l1EL *dsl.L1ELNode, l2EL *dsl.L2ELNode) *daFootprintEnv {
systemConfig := bindings.NewBindings[daFootprintSystemConfig](
bindings.WithClient(l1EL.EthClient()),
bindings.WithTo(l2Network.Escape().Deployment().SystemConfigProxyAddr()),
bindings.WithTest(t),
)

l1Block := bindings.NewBindings[daFootprintL1Block](
bindings.WithClient(l2EL.Escape().EthClient()),
bindings.WithTo(common.HexToAddress("0x4200000000000000000000000000000000000015")),
bindings.WithTest(t),
)

return &daFootprintEnv{
l1Client: l1EL,
l2Network: l2Network,
l2EL: l2EL,
systemConfig: systemConfig,
l1Block: l1Block,
}
}

func (env *daFootprintEnv) checkCompatibility(t devtest.T) {
// Ensure getters exist on both L1 SystemConfig and L2 L1Block
_, err := contractio.Read(env.systemConfig.DAFootprintGasScalar(), t.Ctx())
t.Require().NoError(err)
_, err = contractio.Read(env.l1Block.DAFootprintGasScalar(), t.Ctx())
t.Require().NoError(err)
}

func (env *daFootprintEnv) getSystemConfigOwner(t devtest.T) *dsl.EOA {
priv := env.l2Network.Escape().Keys().Secret(devkeys.SystemConfigOwner.Key(env.l2Network.ChainID().ToBig()))
return dsl.NewKey(t, priv).User(env.l1Client)
}

func (env *daFootprintEnv) setDAFootprintGasScalarViaSystemConfig(t devtest.T, scalar uint16) *types.Receipt {
owner := env.getSystemConfigOwner(t)
rec, err := contractio.Write(env.systemConfig.SetDAFootprintGasScalar(scalar), t.Ctx(), owner.Plan())
t.Require().NoError(err, "SetDAFootprintGasScalar transaction failed")
t.Logf("Set DA footprint gas scalar on L1: scalar=%d", scalar)
return rec
}

// expectL1BlockDAFootprintGasScalar expects the given DA footprint gas scalar to be set in the L1Block contract.
func (env *daFootprintEnv) expectL1BlockDAFootprintGasScalar(t devtest.T, expected uint16) {
current, err := contractio.Read(env.l1Block.DAFootprintGasScalar(), t.Ctx())
t.Require().NoError(err, "Failed to read DA footprint gas scalar from L1Block")
t.Require().Equal(expected, current)
}

func TestDAFootprint(gt *testing.T) {
t := devtest.SerialT(gt)
sys := presets.NewMinimal(t)
require := t.Require()

err := dsl.RequiresL2Fork(t.Ctx(), sys, 0, rollup.Jovian)
require.NoError(err, "Jovian fork must be active for this test")

env := newDAFootprintEnv(t, sys.L2Chain, sys.L1EL, sys.L2EL)
env.checkCompatibility(t)

systemOwner := env.getSystemConfigOwner(t)
sys.FunderL1.FundAtLeast(systemOwner, eth.OneTenthEther)
l2BlockTime := time.Duration(sys.L2Chain.Escape().RollupConfig().BlockTime) * time.Second
sys.L2EL.WaitForOnline()
ethClient := sys.L2EL.Escape().EthClient()

sys.L2EL.WaitForOnline()
s1000 := uint16(1000)
s0 := uint16(0)
cases := []struct {
name string
setScalar *uint16
expected uint16
}{
{"DefaultScalar", nil, uint16(eth.DAFootprintGasScalarDefault)},
{"Scalar1000", &s1000, uint16(1000)},
{"ScalarZeroUsesDefault", &s0, uint16(eth.DAFootprintGasScalarDefault)},
}

var wg sync.WaitGroup
defer wg.Wait()

ctx, cancel := context.WithTimeout(t.Ctx(), time.Minute)
defer cancel()
t = t.WithCtx(ctx)

wg.Add(1)
go func() {
defer wg.Done()
eoa := sys.FunderL2.NewFundedEOA(eth.OneEther.Mul(100))
includer := txinclude.NewPersistent(txinclude.NewPkSigner(eoa.Key().Priv(), eoa.ChainID().ToBig()), struct {
*txinclude.Resubmitter
*txinclude.Monitor
}{
txinclude.NewResubmitter(ethClient, l2BlockTime),
txinclude.NewMonitor(ethClient, l2BlockTime),
})
loadtest.NewBurst(l2BlockTime).Run(t, NewCalldataSpammer(loadtest.NewSyncEOA(includer, eoa.Plan())))
}()

for range time.Tick(l2BlockTime) {
info, txs, err := ethClient.InfoAndTxsByLabel(t.Ctx(), eth.Unsafe)
t.Require().NoError(err)

blockGasUsed := info.GasUsed()

receipt, err := ethClient.TransactionReceipt(t.Ctx(), txs[len(txs)-1].Hash())
t.Require().NoError(err)
// The last tx's cumulative gas used is the block's total transaction gas used.
totalTxGasUsed := receipt.CumulativeGasUsed
if totalTxGasUsed == blockGasUsed {
t.Logf("Block %d:%s doesn't have excess gas used, trying next...", info.NumberU64(), info.Hash())
continue
}

var totalDAFootprint uint64
for _, tx := range txs {
if tx.IsDepositTx() {
continue
for _, tc := range cases {
t.Run(tc.name, func(t devtest.T) {
if tc.setScalar != nil {
rec := env.setDAFootprintGasScalarViaSystemConfig(t, *tc.setScalar)
// Wait for change to propagate to L2
env.l2EL.WaitL1OriginReached(eth.Unsafe, rec.BlockNumber.Uint64(), 20)
} else {
sys.L2EL.WaitForBlockNumber(2) // make sure we don't assert on genesis or first block
}
env.expectL1BlockDAFootprintGasScalar(t, tc.expected)

var wg sync.WaitGroup
defer wg.Wait()

ctx, cancel := context.WithTimeout(t.Ctx(), time.Minute)
defer cancel()
t = t.WithCtx(ctx)

wg.Add(1)
go func() {
defer wg.Done()
eoa := sys.FunderL2.NewFundedEOA(eth.OneEther.Mul(100))
includer := txinclude.NewPersistent(txinclude.NewPkSigner(eoa.Key().Priv(), eoa.ChainID().ToBig()), struct {
*txinclude.Resubmitter
*txinclude.Monitor
}{
txinclude.NewResubmitter(ethClient, l2BlockTime),
txinclude.NewMonitor(ethClient, l2BlockTime),
})
loadtest.NewBurst(l2BlockTime).Run(t, NewCalldataSpammer(loadtest.NewSyncEOA(includer, eoa.Plan())))
}()

rollupCfg := sys.L2Chain.Escape().RollupConfig()
gasTarget := rollupCfg.Genesis.SystemConfig.GasLimit / rollupCfg.ChainOpConfig.EIP1559Elasticity

var blockDAFootprint uint64
info := sys.L2EL.WaitForUnsafe(func(info eth.BlockInfo) (bool, error) {
blockGasUsed := info.GasUsed()
blobGasUsed := info.BlobGasUsed()
t.Require().NotNil(blobGasUsed, "blobGasUsed must not be nil for Jovian chains")
blockDAFootprint = *blobGasUsed
if blockDAFootprint <= blockGasUsed {
t.Logf("Block %s has DA footprint (%d) <= gasUsed (%d), trying next...",
eth.ToBlockID(info), blockDAFootprint, blockGasUsed)
return false, nil
}
if blockDAFootprint <= gasTarget {
t.Logf("Block %s has DA footprint (%d) <= gasTarget (%d), trying next...",
eth.ToBlockID(info), blockDAFootprint, gasTarget)
return false, nil
}
return true, nil
})

_, txs, err := ethClient.InfoAndTxsByHash(t.Ctx(), info.Hash())
t.Require().NoError(err)

var totalDAFootprint uint64
for _, tx := range txs {
if tx.IsDepositTx() {
continue
}
totalDAFootprint += tx.RollupCostData().EstimatedDASize().Uint64() * uint64(tc.expected)
}
t.Logf("Block %s has header/calculated DA footprint %d/%d",
eth.ToBlockID(info), blockDAFootprint, totalDAFootprint)
t.Require().Equal(totalDAFootprint, blockDAFootprint, "Calculated DA footprint doesn't match block header DA footprint")

// Check base fee calculation of next block
// Calculate expected base fee as:
// parentBaseFee + max(1, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)
var (
baseFee = new(big.Int)
denom = new(big.Int)
)
baseFee.SetUint64(blockDAFootprint - gasTarget) // gasUsedDelta
baseFee.Mul(baseFee, info.BaseFee())
baseFee.Div(baseFee, denom.SetUint64(gasTarget))
baseFee.Div(baseFee, denom.SetUint64(*rollupCfg.ChainOpConfig.EIP1559DenominatorCanyon))
if baseFee.Cmp(common.Big1) < 0 {
baseFee.Add(info.BaseFee(), common.Big1)
} else {
baseFee.Add(info.BaseFee(), baseFee)
}
totalDAFootprint += tx.RollupCostData().EstimatedDASize().Uint64() * eth.DAFootprintGasScalarDefault
}
t.Logf("Block %d:%s has DA footprint exceeding cumulative tx gas used, %d > %d", info.NumberU64(), info.Hash(),
totalDAFootprint, totalTxGasUsed)
t.Require().Less(totalTxGasUsed, blockGasUsed)
t.Require().Equal(totalDAFootprint, blockGasUsed)
return
t.Logf("Expected base fee: %s", baseFee)

next := sys.L2EL.WaitForBlockNumber(info.NumberU64() + 1)
t.Require().Equal(baseFee, next.BaseFee(), "Wrong base fee")
})
}
t.Require().FailNow("Never saw a block with gasUsed > total transaction gasUsed.")
}
Loading